Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -595,7 +601,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;
Expand Down Expand Up @@ -629,7 +636,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).
Expand Down Expand Up @@ -661,7 +672,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;
Expand All @@ -673,7 +684,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) {
Expand All @@ -692,7 +703,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
Expand All @@ -712,7 +723,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;
Expand Down Expand Up @@ -800,7 +811,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) {
Expand Down
57 changes: 57 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading