Merge pull request #1357 from TibixDev/pr/shared-import-spans

fix(agc): retry failed host imports after range replacement
This commit is contained in:
Dean 2026-10-13 00:43:28 +13:00 committed by GitHub
commit ac2ad045fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 8 deletions

View File

@ -142,6 +142,12 @@ bool addressSpaceCacheEnabled() {
return !disabled;
}
struct ImportFailure {
std::uint64_t base;
std::uint64_t bytes;
GuestAllocations::Mapped ranges;
};
// Imports persist across draws, keyed by allocation base, and are dropped when their allocation leaves
// the registered set; a dropped import is destroyed once the recorded work that may read it completed.
struct HostImports {
@ -152,7 +158,7 @@ struct HostImports {
std::map<std::uint64_t, HostImport> imports;
std::map<VkDevice, std::vector<std::shared_ptr<RetiredImport>>> parked;
std::uint64_t liveBytes = 0;
std::set<std::uint64_t> failed;
std::map<std::uint64_t, ImportFailure> failed;
std::set<std::uint64_t> unaliasedSpans;
// Registry generation the imports were last reconciled with.
std::uint64_t refreshedGeneration = 0;
@ -615,7 +621,8 @@ bool runCovers(const GuestAllocations::Lease& lease, std::uint64_t begin, std::u
return !run.ranges.empty() && end <= run.base + run.bytes;
}
bool sameRun(const HostImport& entry, const ImportRun& run) {
template<typename Entry>
bool sameRun(const Entry& entry, const ImportRun& run) {
if (run.base != entry.base || run.bytes != entry.bytes || run.ranges.size() != entry.ranges.size()) return false;
for (std::size_t index = 0; index < run.ranges.size(); ++index) {
if (entry.ranges[index].owner_before(run.ranges[index]) || run.ranges[index].owner_before(entry.ranges[index])) return false;
@ -649,7 +656,11 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
}
if (auto it = state.imports.lower_bound(base); it != state.imports.begin() && std::prev(it)->second.base + std::prev(it)->second.bytes > base) retireImport(context.device, state, std::prev(it), lease);
const auto alignment = context.hostImportAlignment;
if (alignment == 0 || base % alignment != 0 || bytes % alignment != 0 || state.failed.contains(base)) return nullptr;
if (alignment == 0 || base % alignment != 0 || bytes % alignment != 0) return nullptr;
if (const auto failed = state.failed.find(base); failed != state.failed.end()) {
if (sameRun(failed->second, run)) return nullptr;
state.failed.erase(failed);
}
// Pinned imports count against the driver's system memory budget; past it ordinary host
// allocations fail, so imports stop at APS5_HOST_IMPORT_MIB (default 6 GiB, which covers the
// registered memory of address-based shaders; past it they copy gigabytes per dispatch).
@ -681,7 +692,7 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
for (std::uint64_t cursor = base; cursor < base + bytes;) {
MEMORY_BASIC_INFORMATION info{};
if (VirtualQuery(reinterpret_cast<const void*>(cursor), &info, sizeof(info)) == 0 || info.State != MEM_COMMIT || (info.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
state.failed.insert(base);
state.failed.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(entry.ranges.begin(), entry.ranges.end())});
return nullptr;
}
const auto protection = info.Protect & 0xffu;
@ -693,7 +704,7 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
cursor = reinterpret_cast<std::uint64_t>(info.BaseAddress) + info.RegionSize;
}
if (!writable && readOnly) {
state.failed.insert(base);
state.failed.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(entry.ranges.begin(), entry.ranges.end())});
return nullptr;
}
if (!writable) {
@ -712,7 +723,7 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
}
#else
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(base), static_cast<std::size_t>(bytes), true)) {
state.failed.insert(base);
state.failed.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(entry.ranges.begin(), entry.ranges.end())});
return nullptr;
}
#endif
@ -732,7 +743,7 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
#ifdef _WIN32
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
#endif
state.failed.insert(base);
state.failed.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(entry.ranges.begin(), entry.ranges.end())});
std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx failed at %s (%d); falling back to copies\n", static_cast<unsigned long long>(base), static_cast<unsigned long long>(bytes), step, static_cast<int>(result));
#ifdef _WIN32
static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr;
@ -820,7 +831,7 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
retireImport(context.device, state, it, lease);
it = next;
}
for (auto it = state.failed.begin(); it != state.failed.end();) it = leasedRangeAt(lease, *it) != nullptr ? std::next(it) : state.failed.erase(it);
for (auto it = state.failed.begin(); it != state.failed.end();) it = sameRun(it->second, importRun(lease, it->first)) ? std::next(it) : state.failed.erase(it);
}
const HostImport* findImport(HostImports& state, std::uint64_t begin, std::uint64_t end) {

View File

@ -119,6 +119,62 @@ void importSpanTests(const Context& context) {
::operator delete(block, std::align_val_t{piece});
}
void failedImportRemapTests(const Context& context, const BdaTestAccess& access) {
constexpr std::size_t piece = 65536;
auto* block = static_cast<std::byte*>(::operator new(4 * piece, std::align_val_t{piece}));
auto importing = context;
importing.hostImportAlignment = piece;
const auto address = reinterpret_cast<std::uintptr_t>(block);
const auto add = [&](std::size_t index) {
GuestAllocations::Mutation mutation;
mutation.Add(block + index * piece, piece, true, true, true);
};
const auto remove = [&](std::size_t index) {
GuestAllocations::Mutation mutation;
mutation.Remove(block + index * piece);
};
SetHostImportWindow(0);
for (std::size_t replaced = 0; replaced < 2; ++replaced) {
add(0);
add(1);
std::weak_ptr<const GuestAllocations::Range> owner;
{
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
const auto found = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return range->address == address + replaced * piece; });
Require(found != lease.end(), "failed import fixture has no registered owner");
owner = *found;
}
access.limitMemory(0);
const auto attempts = access.allocationAttempts();
Require(HostImportFor(importing, address, 2 * piece) == nullptr && access.allocationAttempts() == attempts + 1, "host import failure was not exercised");
Require(owner.use_count() == 1, "a failed import pins its owner");
Require(HostImportFor(importing, address, 2 * piece) == nullptr && access.allocationAttempts() == attempts + 1, "an unchanged failed import was retried");
add(3);
Require(HostImportFor(importing, address, 2 * piece) == nullptr && access.allocationAttempts() == attempts + 1, "an unrelated mapping retried a failed import");
remove(replaced);
Require(owner.expired(), "a failed import kept the replaced owner alive");
add(replaced);
access.limitMemory(std::nullopt);
const auto* imported = HostImportFor(importing, address, 2 * piece);
Require(imported != nullptr && imported->base == address && imported->bytes == 2 * piece && access.allocationAttempts() == attempts + 2, "same-address remap retained a failed import decision");
remove(0);
remove(1);
remove(3);
ClearHostImports(importing.device);
}
add(0);
add(1);
access.limitMemory(0);
Require(HostImportFor(importing, address, 2 * piece) == nullptr, "span failure was not exercised");
remove(1);
access.limitMemory(std::nullopt);
const auto* shortened = HostImportFor(importing, address, piece);
Require(shortened != nullptr && shortened->bytes == piece, "a failed span suppressed an import of its remaining range");
remove(0);
ClearHostImports(importing.device);
::operator delete(block, std::align_val_t{piece});
}
void importWindowTests(const Context& context) {
constexpr std::size_t piece = 65536;
constexpr std::size_t window = 2 * piece;
@ -1105,6 +1161,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
}
importCrossingTests(context, access);
importSpanTests(context);
failedImportRemapTests(context, access);
importWindowTests(context);
heapMirrorTests(context, access);
importedHeapMirrorTests(context, access);