Merge pull request #1489 from Celegans12/fix/agc-merged-range-past-import

fix(agc): copy an imported range once a merge extends it past its import
This commit is contained in:
Dean 2026-10-10 08:05:33 +13:00 committed by GitHub
commit 00dbf93742
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 58 additions and 0 deletions

View File

@ -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.

View File

@ -15,6 +15,7 @@
#include <functional>
#include <iostream>
#include <limits>
#include <new>
#include <string>
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<std::uint8_t*>(block);
std::memset(guest, 0x11, half);
std::memset(guest + half, 0x22, half);
const auto first = reinterpret_cast<std::uintptr_t>(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);

View File

@ -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<std::string_view, PFN_vkVoidFunction> table{
{"vkGetBufferDeviceAddressKHR", reinterpret_cast<PFN_vkVoidFunction>(mockGetBufferDeviceAddress)},
{"vkGetMemoryHostPointerPropertiesEXT", reinterpret_cast<PFN_vkVoidFunction>(mockGetMemoryHostPointerProperties)},
{"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(mockCreateBuffer)},
{"vkGetBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(mockGetBufferMemoryRequirements)},
{"vkAllocateMemory", reinterpret_cast<PFN_vkVoidFunction>(mockAllocateMemory)},