diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 3aa4c9b3782..1de53a935fb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -995,6 +995,22 @@ ImportRun windowRun(const HostImports& state, ImportRun run, std::uint64_t begin } } if (windowBegin % alignment != 0 || (windowEnd - windowBegin) % alignment != 0) return run; + const auto committed = GuestMemory::DescribeCommitted(windowBegin, windowEnd - windowBegin); + if (!committed.whole) { + const auto part = std::find_if(committed.ranges.begin(), committed.ranges.end(), [&](const auto& span) { return span.first <= begin && span.second >= end; }); + if (part == committed.ranges.end()) return run; + const auto clippedBegin = part->first + (alignment - part->first % alignment) % alignment; + const auto clippedEnd = part->second - part->second % alignment; + if (clippedBegin > begin || clippedEnd < end) return run; + auto it = state.imports.upper_bound(clippedBegin); + if (it != state.imports.begin()) --it; + for (; it != state.imports.end() && it->first < clippedEnd; ++it) { + const auto importEnd = it->second.base + it->second.bytes; + if (importEnd > clippedBegin && (it->second.base < clippedBegin || importEnd > clippedEnd)) return run; + } + windowBegin = clippedBegin; + windowEnd = clippedEnd; + } run.base = windowBegin; run.bytes = windowEnd - windowBegin; return run; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 98aaf121d1c..0c0e2211b1e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -1478,6 +1478,66 @@ void releaseSparse(std::byte* block, std::size_t reserved) { #endif } +void importPartlyCommittedWindowTests(const Context& context) { +#ifdef _WIN32 + constexpr std::size_t piece = 65536; +#else + constexpr std::size_t piece = 4096; +#endif + constexpr std::size_t reserved = 8 * piece; + auto importing = context; + importing.hostImportAlignment = piece; + auto* block = reserveSparse(reserved, piece); + struct Cleanup { + std::byte* block; + Context importing; + ~Cleanup() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(importing, reinterpret_cast(block), 1); + SetHostImportRangeWindow(std::uint64_t{64} << 20u); + releaseSparse(block, reserved); + } + } cleanup{block, importing}; + { + GuestAllocations::Mutation mutation; + mutation.Add(block, reserved, true, false, true); + } + std::memset(block, 0x35, piece); + SetHostImportRangeWindow(2 * piece); + const auto base = reinterpret_cast(block); + Require(AgcDriver::GuestMemory::DescribeCommitted(base, piece).whole && !AgcDriver::GuestMemory::DescribeCommitted(base, 2 * piece).whole, "the import window test has no uncommitted tail"); + const auto* imported = HostImportFor(importing, base + 16, 32); + Require(imported != nullptr && imported->base == base && imported->bytes == piece, "a committed access was refused because its import window includes uncommitted pages"); + const auto buffer = imported->buffer; + Require(HostImportFor(importing, base + piece - 16, 32) == nullptr, "an import crossed from committed bytes into an uncommitted page"); + const auto* repeated = HostImportFor(importing, base + 32, 32); + Require(repeated != nullptr && repeated->buffer == buffer, "a repeated committed access did not retain its narrowed import"); +#ifdef _WIN32 + Require(VirtualAlloc(block + 4 * piece, piece, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot commit the separate import island"); +#else + Require(mprotect(block + 4 * piece, piece, PROT_READ | PROT_WRITE) == 0, "cannot commit the separate import island"); +#endif + const auto* island = HostImportFor(importing, base + 4 * piece + 16, 32); + Require(island != nullptr && island->base == base + 4 * piece && island->bytes == piece && island->buffer != buffer, "a separate committed island did not get its own import"); + Require(HostImportFor(importing, base + piece - 16, 3 * piece + 32) == nullptr, "an import joined committed islands across uncommitted pages"); +#ifdef _WIN32 + Require(VirtualAlloc(block + piece, piece, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot grow the committed import prefix"); +#else + Require(mprotect(block + piece, piece, PROT_READ | PROT_WRITE) == 0, "cannot grow the committed import prefix"); +#endif + Require(HostImportFor(importing, base + piece - 16, 32) == nullptr, "growing a window replaced a held import without a recorder"); + { + GuestBufferMemory nextBuild(importing); + nextBuild.Upload(false); + nextBuild.WriteBack(); + } + const auto* grown = HostImportFor(importing, base + piece - 16, 32); + Require(grown != nullptr && grown->base == base && grown->bytes == 2 * piece, "a newly committed window did not replace the narrower import"); +} + void sparseHeapTests(const Context& context, const BdaTestAccess& access) { constexpr std::size_t reserved = 64u << 20u; constexpr std::size_t committed = 65536; @@ -1936,6 +1996,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importJoinTests(context); importWholeRangeOverWindowTests(context); importRefusedWholeRangeTests(context); + importPartlyCommittedWindowTests(context); importNeighbouringEdgesTests(context); importAtMapOverBudgetTests(context); heapMirrorTests(context, access); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp index 57396daef2a..ce987c58531 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/HostImportWindows.cpp @@ -9,6 +9,8 @@ #define NOMINMAX #endif #include +#else +#include #endif #include #include @@ -40,14 +42,18 @@ std::string Hex(std::uint64_t value) { class GuestBlock { public: - explicit GuestBlock(std::uint64_t size) : bytes(size) { + explicit GuestBlock(std::uint64_t size, std::uint64_t committedBytes = 0) : bytes(size), committed(committedBytes == 0 ? size : committedBytes) { #ifdef _WIN32 - block = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + block = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS)); + Require(block != nullptr && VirtualAlloc(block, committed, MEM_COMMIT, PAGE_READWRITE) != nullptr, "host import windows: cannot commit guest memory"); #else block = static_cast(std::aligned_alloc(GuestAlignment, bytes)); #endif Require(block != nullptr, "host import windows: cannot allocate a guest block of " + Hex(bytes)); - std::memset(block, 0, bytes); +#ifndef _WIN32 + if (committed < bytes) Require(mprotect(block + committed, bytes - committed, PROT_NONE) == 0, "host import windows: cannot protect uncommitted guest memory"); +#endif + std::memset(block, 0, committed); GuestAllocations::Mutation().Add(block, bytes, true, true, true); } @@ -56,6 +62,7 @@ class GuestBlock { #ifdef _WIN32 VirtualFree(block, 0, MEM_RELEASE); #else + if (committed < bytes) mprotect(block + committed, bytes - committed, PROT_READ | PROT_WRITE); std::free(block); #endif } @@ -69,6 +76,7 @@ class GuestBlock { private: std::uint8_t* block = nullptr; std::uint64_t bytes = 0; + std::uint64_t committed = 0; }; enum class Outcome : std::uint8_t { NoDevice, NoImport, Passed }; @@ -124,6 +132,21 @@ Outcome WindowsFollowTheirRange() { return Outcome::Passed; } +Outcome PartlyCommittedWindow() { + GuestBlock guest(BlockBytes, GuestAlignment); + auto device = OpenVulkanTestDevice(); + if (!device) return Outcome::NoDevice; + Require(!AgcDriver::GuestMemory::DescribeCommitted(guest.Address(), WindowBytes).whole, "host import windows: the partial window has no inaccessible tail"); + Require(Fill(*device, guest.Address()), "host import windows: cannot fill the committed prefix"); + const auto probe = Probe(*device); + Require(Graphics::HostImportCovers(probe, guest.Address(), ProbeBytes), "host import windows: a committed prefix fell back to copies because of an inaccessible tail"); + std::array actual{}; + std::memcpy(actual.data(), guest.Data(), sizeof(actual)); + Require(actual == std::array{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u}, "host import windows: the imported committed prefix lost its GPU stores"); + Graphics::ClearHostImports(device->Device()); + return Outcome::Passed; +} + } int main() { @@ -136,6 +159,7 @@ int main() { } const auto windows = WindowsFollowTheirRange(); Require(windows == Outcome::Passed, "host import windows: the device stopped importing guest memory between two scenarios"); + Require(PartlyCommittedWindow() == Outcome::Passed, "host import windows: the partial window lost its device"); std::puts("host import windows tests passed"); return 0; } catch (const std::exception& error) {