From 52eb4272dea5304db1c5e2fd33e8d00f968fdb3e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:34:35 +0200 Subject: [PATCH] fix(agc): copy an imported range once a merge extends it past its import An address-based build maps each imported registered range through its host import. UploadPrepare merges a descriptor range or snapshot overlapping such a range into the range's region, and the region kept the import even when the merge took it past the import's end, as a V# crossing into the next registered range does. Descriptor then bound the import's buffer beyond its size, and the BDA table mapped the whole region, the next range included, through the import's device address. A merge that extends a region past its end now drops the import. The region then takes the import lookup and, with no import covering it, the copy path, as a range crossing an image mirror's end already does. Like every copied range, it is read-only to pointer stores. --- .../Graphics/src/GuestBufferMemory.cpp | 1 + .../libSceAgcDriver/tests/BdaResources.cpp | 50 +++++++++++++++++++ .../prx/libSceAgcDriver/tests/Graphics.cpp | 7 +++ 3 files changed, 58 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 92b900e7d..91dab33a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1966,6 +1966,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) { previous.hostBacked = true; } mergeBacked(previous, region); + if (region.end > previous.end) previous.direct = nullptr; previous.atomic = previous.atomic || region.atomic; // A range starting before the mirror (only possible after the swap) keeps its prefix; the // earlier merged region ends at or before it, so the merged list stays sorted. diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index f803903fc..c3b5517eb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace { @@ -32,6 +33,54 @@ void reject(TAction action, const char* reason) { throw std::runtime_error(std::string("expected BDA rejection: ") + reason); } +void importCrossingTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t half = 65536; + void* block = ::operator new(2 * half, std::align_val_t{half}); + auto* guest = static_cast(block); + std::memset(guest, 0x11, half); + std::memset(guest + half, 0x22, half); + const auto first = reinterpret_cast(block); + const auto second = first + half; + auto importing = context; + importing.hostImportAlignment = half; + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + for (auto* range : {guest, guest + half}) { + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, range, half, true, false); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, range); + } + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + registry(true); + { + GuestBufferMemory leased(importing); + leased.AcquireRegistered(); + Require(HostImportCovers(importing, first, half) && HostImportCovers(importing, second, half), "the registered ranges were not imported"); + const auto secondImport = *HostImportFor(importing, second, half); + leased.AddReadable(second - 16, 32); + leased.AddReadable(second + 64, 16); + leased.Upload(true); + std::uint32_t adjustment = 0; + const auto crossing = leased.Descriptor(second - 16, 32, adjustment); + const auto crossingBytes = access.bytes(crossing.buffer); + Require(crossing.offset + crossing.range <= crossingBytes.size(), "a view crossing into the next registered range is bound past its host import"); + Require(crossingBytes[crossing.offset + adjustment + 15] == std::byte{0x11} && crossingBytes[crossing.offset + adjustment + 16] == std::byte{0x22}, "a view crossing into the next registered range misses its bytes"); + const auto inside = leased.Descriptor(second + 64, 16, adjustment); + const auto insideBytes = access.bytes(inside.buffer); + Require(inside.offset + inside.range <= insideBytes.size() && (inside.buffer == secondImport.buffer ? inside.offset + adjustment == 64 : insideBytes[inside.offset + adjustment] == std::byte{0x22}), "a view in the second registered range is bound past its buffer or misses its bytes"); + for (const auto& range : leased.AddressRanges()) { + if (range.end <= first || range.begin >= second + half) continue; + const auto mapped = access.addressBytes(range.deviceAddress); + Require(mapped.size() >= range.end - range.begin, "a BDA range runs past the end of its host import"); + if (range.begin <= second && second < range.end && range.deviceAddress != secondImport.address) Require(mapped[second - range.begin] == std::byte{0x22}, "the BDA range over the second registered range misses its bytes"); + } + leased.WriteBack(); + } + registry(false); + Require(HostImportFor(importing, first, half) == nullptr && !HostImportCovers(importing, second, half), "the host imports outlived their ranges"); + ::operator delete(block, std::align_val_t{half}); +} + ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) { return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false}; } @@ -385,6 +434,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { const auto dropped = AddressSpaceCounters(); if (dropped.enabled) Require(dropped.waiterDrops == after.waiterDrops + 1 && LeaseCounters().cacheDrops == dropped.waiterDrops, "the free did not drop the cached address space"); } + importCrossingTests(context, access); heapMirrorTests(context, access); Require(AddressCopyOverflow({{0x1000, 0x3000, 0x2000, "uncommitted pages"}}, 0x2000).empty(), "copies within the limit were refused"); const auto copies = AddressCopyOverflow({{0x1000, 0x2000, 0x1000, "not mirrored"}, {0x10000, 0x30000, 0x18000, "uncommitted pages"}}, 0x2000); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index a035d3bf6..a22b85ecd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1300,6 +1300,12 @@ VKAPI_ATTR void VKAPI_CALL mockCmdBindDescriptorSets(VkCommandBuffer, VkPipeline mock.boundSets = count; } +VKAPI_ATTR VkResult VKAPI_CALL mockGetMemoryHostPointerProperties(VkDevice, VkExternalMemoryHandleTypeFlagBits type, const void* pointer, VkMemoryHostPointerPropertiesEXT* properties) { + Require(type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT && pointer != nullptr, "invalid host pointer import query"); + properties->memoryTypeBits = 1; + return VK_SUCCESS; +} + VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const VkBufferDeviceAddressInfo* info) { Require(mock.bufferMemory.contains(info->buffer), "BDA buffer was not bound"); Require((mock.bufferUsage.at(info->buffer) & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0, "BDA buffer usage is missing"); @@ -1369,6 +1375,7 @@ VKAPI_ATTR void VKAPI_CALL mockCmdUpdateBuffer(VkCommandBuffer, VkBuffer buffer, PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { static const std::map table{ {"vkGetBufferDeviceAddressKHR", reinterpret_cast(mockGetBufferDeviceAddress)}, + {"vkGetMemoryHostPointerPropertiesEXT", reinterpret_cast(mockGetMemoryHostPointerProperties)}, {"vkCreateBuffer", reinterpret_cast(mockCreateBuffer)}, {"vkGetBufferMemoryRequirements", reinterpret_cast(mockGetBufferMemoryRequirements)}, {"vkAllocateMemory", reinterpret_cast(mockAllocateMemory)},