diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 2782537b10..3d086d3763 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1383,6 +1383,14 @@ if(BUILD_TESTING) set_tests_properties(agc_collect_mapping_history_ring PROPERTIES ENVIRONMENT "APS5_NO_EPOCH_COLLECT_MAP=1") set_tests_properties(agc_collect_mapping_history_shared PROPERTIES ENVIRONMENT "APS5_SHARED_COLLECT_MEMO=1") set_tests_properties(agc_collect_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1") + if(WIN32) + foreach(mode IN ITEMS cached uncached) + add_test(NAME agc_hint_mapping_history_${mode} COMMAND agc_driver_recorder_tests --hint-mapping-history-only) + set_tests_properties(agc_hint_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endforeach() + set_tests_properties(agc_hint_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1") + endif() add_test(NAME agc_storage_kept_unit_settle COMMAND agc_driver_recorder_tests --kept-unit-settle-only) set_tests_properties(agc_storage_kept_unit_settle PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) if(WIN32) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 36d57da9c9..a937f56111 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -873,6 +873,7 @@ struct WriteTracker { std::uintptr_t end = 0; std::uint64_t epoch = 0; std::uint64_t unwatched = 0; + std::uint64_t mapping = 0; }; std::array memo{}; std::size_t nextMemo = 0; @@ -1347,19 +1348,20 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi // must not differ between them. const auto epoch = currentCollectEpoch(); const auto unwatched = unwatchSerial.load(std::memory_order_acquire); + const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); const bool useMemo = memoized && collectMemoEnabled() && bytes != 0; if (useMemo && !sharedCollectMemo()) { // An entry exists only for a completed walk of an in-arena range, so the tracker is // initialized and watched; nothing below the lock needs asking. if (epochCollectMapEnabled()) { const auto& map = epochCollectMap(epoch); - if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && stop <= found->second.end) { + if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && found->second.mapping == mapping && stop <= found->second.end) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); return tracker.generation.load(std::memory_order_relaxed); } } for (const auto& entry : threadCollectMemo.entries) { - if (entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end) { + if (entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end) { // The current generation, not the memoized one: blocks stamped since (MarkWritten, an // overlapping collect) were written before this caller reads, and an older value would // make every later UnchangedSince fail until the epoch ends. @@ -1374,7 +1376,7 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi auto cursor = first; if (useMemo && sharedCollectMemo()) { for (const auto& entry : tracker.memo) { - if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.begin <= cursor && stop <= entry.end) { + if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.mapping == mapping && entry.begin <= cursor && stop <= entry.end) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); return tracker.generation; } @@ -1395,12 +1397,12 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi // Only a completed walk is remembered; a failed one (uncommitted pages) returned 0 above. if (collectMemoEnabled()) { const auto serial = unwatchSerial.load(std::memory_order_relaxed); - if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial}; + if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial, mapping}; else { - threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial}; + threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial, mapping}; if (epochCollectMapEnabled()) { auto& entry = epochCollectMap(epoch).ranges[first]; - if (entry.unwatched != serial || entry.end < stop) entry = {first, stop, epoch, serial}; + if (entry.unwatched != serial || entry.mapping != mapping || entry.end < stop) entry = {first, stop, epoch, serial, mapping}; } } } @@ -1826,15 +1828,16 @@ std::uint64_t UntrackedHint(std::uint64_t address, std::size_t bytes) { tracker.initialize(); if (!tracker.watched || !tracker.hinted(first, stop)) return 0; const auto unwatched = unwatchSerial.load(std::memory_order_relaxed); + const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); const bool memoized = collectMemoEnabled() && std::any_of(threadHintMemo.entries.begin(), threadHintMemo.entries.end(), [&](const WriteTracker::Memo& entry) { - return entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end; + return entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end; }); if (memoized) { collectMemoHits.fetch_add(1, std::memory_order_relaxed); } else { const TimedAccess timed(CounterCollect, bytes); if (!walkHints(tracker, first, stop)) return 0; - if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched}; + if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched, mapping}; } return tracker.newestHint(first, stop); #else diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 5edb320d3a..3aee54c8fc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -169,6 +169,41 @@ std::optional selectDevice(std::span(AllocateWatched(bytes, 65536)); + if (memory == nullptr) return false; + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + struct Cleanup { + std::uint8_t* memory; + ~Cleanup() { + GuestAllocations::Mutation().Remove(memory); + ReleaseWatched(memory, bytes); + } + } cleanup{memory}; + const auto address = reinterpret_cast(memory); + std::memset(memory, 0x11, bytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + AgcDriver::GuestMemory::Unwatch(address, bytes); + const auto before = AgcDriver::GuestMemory::UntrackedHint(address, bytes); + Require(before != 0, "the hint mapping fixture has no write hint"); + Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == before, "an unchanged hint range advanced its history"); + memory[4096] = 0x22; + GuestAllocations::Mutation().Remove(memory); + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + const auto after = AgcDriver::GuestMemory::UntrackedHint(address, bytes); + Require(after > before, "a cached hint walk hid a CPU store across allocation replacement"); + Require(memory[4096] == 0x22 && memory[bytes - 4096] == 0x11, "collecting replacement hints changed guest bytes"); + GuestAllocations::Mutation().Remove(memory); + GuestAllocations::Mutation().Add(memory, bytes, true, true, false); + Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == after, "allocation replacement invented a CPU write hint"); + return true; +#else + return false; +#endif +} + void deviceSelectionTests() { const auto properties = [](const char* name, VkPhysicalDeviceType type) { VkPhysicalDeviceProperties result{}; @@ -9672,6 +9707,11 @@ int main(int argc, char** argv) { std::cout << "collect mapping history tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--hint-mapping-history-only") { + if (!hintMappingHistoryTests()) return 77; + std::cout << "hint mapping history tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--device-selection-only") { deviceSelectionTests(); std::cout << "recorder device selection tests passed\n";