Merge pull request #6097 from XD3V3LOP3RX/fix/host-import-probe-use-stamp

fix(graphics): keep discarded import probes from pinning windows
This commit is contained in:
Adrià authored and GitHub committed 2026-10-18 18:19:35 +02:00
commit 39ff296960
2 files changed
+155 -2

No files matched your search

@@ -1400,7 +1400,10 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
if (fresh && end != 0) {
if (const auto* current = findImport(state, address, end)) return current;
} else if (!fresh) {
if (const auto* current = findImport(state, address, address + 1); current != nullptr && current->joined && sameRange(*current, lease)) return current;
if (const auto* current = peekImport(state, address, address + 1); current != nullptr && current->joined && sameRange(*current, lease)) {
current->lastUse = ++state.useCounter;
return current;
}
}
auto run = importRun(lease, address);
// Without an end the caller views the whole run through the import (AcquireRegistered): a window that holds the
@@ -1431,7 +1434,10 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
failures.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(run.ranges.begin(), run.ranges.end())});
return nullptr;
}
if (const auto found = state.imports.find(base); found != state.imports.end() && sameRun(found->second, run)) return &found->second;
if (const auto found = state.imports.find(base); found != state.imports.end() && sameRun(found->second, run)) {
found->second.lastUse = ++state.useCounter;
return &found->second;
}
if (alignment == 0 || base % alignment != 0 || bytes % alignment != 0) return nullptr;
if (const auto failed = failures.find(base); failed != failures.end()) {
if (sameRun(failed->second, run)) return nullptr;
@@ -22,6 +22,7 @@
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <new>
#include <string>
#include <vector>
@@ -739,6 +740,151 @@ void importWholeRangeOverWindowTests(const Context& context) {
leased.WriteBack();
}
struct ObservedHostImport {
const void* pointer;
VkDeviceSize bytes;
};
class ImportAllocationProbe {
public:
explicit ImportAllocationProbe(Context& context) : context(context), original(context.deviceProc) {
Require(active == nullptr && original != nullptr, "invalid import allocation probe scope");
active = this;
context.deviceProc = Resolve;
}
~ImportAllocationProbe() {
context.deviceProc = original;
active = nullptr;
}
ImportAllocationProbe(const ImportAllocationProbe&) = delete;
ImportAllocationProbe& operator=(const ImportAllocationProbe&) = delete;
std::vector<ObservedHostImport> calls;
private:
static PFN_vkVoidFunction VKAPI_CALL Resolve(VkDevice device, const char* name) {
Require(active != nullptr, "import allocation probe escaped its scope");
if (std::strcmp(name, "vkAllocateMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(Allocate);
return active->original(device, name);
}
static VkResult VKAPI_CALL Allocate(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) {
Require(active != nullptr, "import allocation callback escaped its scope");
for (const auto* next = static_cast<const VkBaseInStructure*>(info->pNext); next != nullptr; next = next->pNext) {
if (next->sType != VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT) continue;
const auto* imported = reinterpret_cast<const VkImportMemoryHostPointerInfoEXT*>(next);
active->calls.push_back({imported->pHostPointer, info->allocationSize});
}
const auto allocate = reinterpret_cast<PFN_vkAllocateMemory>(active->original(device, "vkAllocateMemory"));
Require(allocate != nullptr, "import allocation probe has no original allocator");
return allocate(device, info, allocator, memory);
}
Context& context;
PFN_vkGetDeviceProcAddr original;
inline static thread_local ImportAllocationProbe* active = nullptr;
};
class UnrelatedImportProbeRange {
public:
UnrelatedImportProbeRange() {
GuestAllocations::Mutation().Add(storage.data(), storage.size(), true, false, false);
}
~UnrelatedImportProbeRange() {
GuestAllocations::Mutation().Remove(storage.data());
}
UnrelatedImportProbeRange(const UnrelatedImportProbeRange&) = delete;
UnrelatedImportProbeRange& operator=(const UnrelatedImportProbeRange&) = delete;
private:
std::array<std::byte, 64> storage{};
};
void importStaleProbeTests(const Context& context, const BdaTestAccess& access) {
for (const auto scenario : {0u, 1u, 2u}) {
const bool held = scenario == 1u;
const bool exactWhole = scenario == 2u;
WindowedRange range(context, 2 * WindowedRange::piece, 2);
ImportAllocationProbe probe(range.importing);
const auto first = range.First();
auto* guest = reinterpret_cast<std::byte*>(first);
for (std::size_t offset = 17u; offset < range.bytes; offset += 4096u) guest[offset] = static_cast<std::byte>((offset / 4096u) & 0xffu);
const std::vector<std::byte> originalGuest(guest, guest + range.bytes);
const auto* window = HostImportFor(range.importing, first, exactWhole ? range.bytes : 32u);
Require(window != nullptr && !window->joined && window->base == first && window->bytes == (exactWhole ? range.bytes : range.window), "the stale-probe fixture did not create its ordinary import extent");
const auto originalBuffer = window->buffer;
const auto originalUse = window->lastUse;
const auto originalSerial = HostImportSerial(range.importing, first, 32u, false);
auto mockContents = access.bytes(window->buffer);
Require(mockContents.size() == window->bytes, "the mock import buffer has an incorrect extent");
std::copy_n(originalGuest.begin(), window->bytes, mockContents.begin());
std::uint32_t scratch = 0x12345678u;
{
GuestBufferMemory copied(range.importing);
copied.AddReadable(reinterpret_cast<std::uintptr_t>(&scratch), sizeof(scratch));
copied.Upload(false);
copied.WriteBack();
}
Require(window->lastUse == originalUse && HostImportSerial(range.importing, first, 32u, false) == originalSerial, "an unrelated copied build touched or replaced the prefix window");
GuestBufferMemory reader(range.importing);
VkDescriptorBufferInfo heldView{};
std::uint32_t heldAdjustment = 0u;
if (held) {
reader.AddReadable(first + 16u, 32u);
reader.Upload(false);
heldView = reader.Descriptor(first + 16u, 32u, heldAdjustment);
Require(heldView.buffer == originalBuffer, "the held control did not bind the prefix window");
}
GuestAllocations::Mapped mapped;
{
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
for (const auto& owner : lease) if (owner->address == first && owner->bytes == range.bytes) mapped.push_back(owner);
}
Require(mapped.size() == 1u, "the stale-probe fixture lost its registered range");
const auto originalOwner = mapped.front().lock();
const auto generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
UnrelatedImportProbeRange unrelated;
Require(GuestAllocations::GuestAllocationsGeneration_nid_postfix() != generation && HostImportsIdentity(range.importing).device == VK_NULL_HANDLE,
"the unrelated registry change did not make imports stale");
const auto attempts = access.allocationAttempts();
probe.calls.clear();
{
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
Require(ImportMappedRanges(range.importing, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false), "the current device did not admit the map-time import pass");
}
Require(mapped.front().lock() == originalOwner, "an unrelated registry change replaced the import's memory owner");
if (held) {
Require(access.allocationAttempts() == attempts && probe.calls.empty(), "a stale probe retired an import held by an actual descriptor");
const auto* kept = HostImportFor(range.importing, first + 16u, 32u);
Require(kept != nullptr && kept->buffer == originalBuffer && kept->bytes == range.window && HostImportSerial(range.importing, first, 32u, false) == originalSerial,
"the actual held import lost its buffer, extent or serial");
std::uint32_t adjustment = 0u;
const auto view = reader.Descriptor(first + 16u, 32u, adjustment);
Require(view.buffer == heldView.buffer && view.offset == heldView.offset && view.range == heldView.range && adjustment == heldAdjustment,
"a map-time stale probe invalidated a descriptor that still uses its import");
const auto contents = access.bytes(view.buffer);
Require(view.offset + adjustment + 32u <= contents.size() && std::memcmp(contents.data() + view.offset + adjustment, reinterpret_cast<const void*>(first + 16u), 32u) == 0,
"the protected descriptor no longer reads the registered bytes");
reader.WriteBack();
} else if (exactWhole) {
Require(access.allocationAttempts() == attempts && probe.calls.empty() && window->lastUse > originalUse,
"accepting the stale exact whole import reallocated it or did not protect its returned pointer");
const auto* kept = HostImportFor(range.importing, first, range.bytes);
Require(kept != nullptr && kept->buffer == originalBuffer && kept->ranges.size() == 1u && kept->ranges.front() == originalOwner && HostImportSerial(range.importing, first, range.bytes, false) == originalSerial,
"accepting the exact whole import changed its native or registered identity");
} else {
Require(access.allocationAttempts() == attempts + 1u, "a discarded stale probe made an unheld prefix window block the whole import");
const auto* whole = HostImportFor(range.importing, first, range.bytes);
Require(whole != nullptr && whole->base == first && whole->bytes == range.bytes && whole->ranges.size() == 1u && whole->ranges.front() == originalOwner,
"the stale-probe repair did not retain the exact registered owner over the whole import");
Require(HostImportSerial(range.importing, first, range.bytes, false) != originalSerial, "replacing the unheld window reused its old import identity");
Require(probe.calls.size() == 1u && probe.calls.front().pointer == reinterpret_cast<const void*>(first) && probe.calls.front().bytes == range.bytes,
"the replacement native import did not reference the exact whole registered memory");
Require(access.bytes(whole->buffer).size() == range.bytes, "the replacement import buffer does not cover its registered extent");
}
Require(scratch == 0x12345678u && std::equal(originalGuest.begin(), originalGuest.end(), guest), "the import probe changed guest bytes or unrelated copied data");
}
}
// An address-based build whose whole-range import does not fit the budget leaves the range's window alone: before,
// every such build retired the window and the next access imported it again.
void importRefusedWholeRangeTests(const Context& context) {
@@ -2068,6 +2214,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
importCoverageHintBudgetTests(context, access);
failedImportJoinProbeBudgetTests(context, access);
importWholeRangeOverWindowTests(context);
importStaleProbeTests(context, access);
importRefusedWholeRangeTests(context);
importNeighbouringEdgesTests(context);
importAtMapOverBudgetTests(context);