graphics: pause memory-tracking spin loops

This commit is contained in:
nmzik 2026-10-07 22:27:36 +04:00
parent a87889f032
commit 006875ed4f
3 changed files with 64 additions and 4 deletions

View File

@ -15,6 +15,12 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__x86_64__)
#include <xmmintrin.h>
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX #ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
@ -62,7 +68,13 @@ class SpinGuard final {
public: public:
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) { explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
while (m_lock.test_and_set(std::memory_order_acquire)) { while (m_lock.test_and_set(std::memory_order_acquire)) {
std::atomic_signal_fence(std::memory_order_seq_cst); #if defined(__x86_64__) || defined(_M_X64)
_mm_pause();
#elif defined(_M_ARM64)
__yield();
#elif defined(__aarch64__)
asm volatile("yield");
#endif
} }
} }
~SpinGuard() { m_lock.clear(std::memory_order_release); } ~SpinGuard() { m_lock.clear(std::memory_order_release); }

View File

@ -9,6 +9,12 @@
#include <mutex> #include <mutex>
#include <utility> #include <utility>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__x86_64__)
#include <xmmintrin.h>
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX #ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
@ -32,7 +38,13 @@ public:
const auto thread = CurrentThread(); const auto thread = CurrentThread();
while (m_lock.test_and_set(std::memory_order_acquire)) { while (m_lock.test_and_set(std::memory_order_acquire)) {
EXIT_NOT_IMPLEMENTED(m_owner.load(std::memory_order_relaxed) == thread); EXIT_NOT_IMPLEMENTED(m_owner.load(std::memory_order_relaxed) == thread);
std::atomic_signal_fence(std::memory_order_seq_cst); #if defined(__x86_64__) || defined(_M_X64)
_mm_pause();
#elif defined(_M_ARM64)
__yield();
#elif defined(__aarch64__)
asm volatile("yield");
#endif
} }
m_owner.store(thread, std::memory_order_relaxed); m_owner.store(thread, std::memory_order_relaxed);
} }

View File

@ -1,12 +1,15 @@
#include "common/virtualMemory.h" #include "common/virtualMemory.h"
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include <barrier>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <mutex>
#include <string> #include <string>
#include <thread>
#include <vector> #include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@ -167,6 +170,7 @@ struct ProtectionCall {
uint64_t size; uint64_t size;
}; };
std::vector<ProtectionCall> g_protection_ranges; std::vector<ProtectionCall> g_protection_ranges;
std::mutex g_protection_log_mutex;
bool ProtectAddressSpace(uint64_t vaddr, uint64_t size, bool ProtectAddressSpace(uint64_t vaddr, uint64_t size,
Common::VirtualMemory::Mode mode) { Common::VirtualMemory::Mode mode) {
@ -177,8 +181,11 @@ bool ProtectAddressSpace(uint64_t vaddr, uint64_t size,
protection = PAGE_READWRITE; protection = PAGE_READWRITE;
} }
DWORD old_protection = 0; DWORD old_protection = 0;
g_protection_calls++; {
g_protection_ranges.push_back({vaddr, size}); std::lock_guard lock(g_protection_log_mutex);
g_protection_calls++;
g_protection_ranges.push_back({vaddr, size});
}
return VirtualProtect(reinterpret_cast<void *>(vaddr), size, protection, return VirtualProtect(reinterpret_cast<void *>(vaddr), size, protection,
&old_protection) != 0; &old_protection) != 0;
} }
@ -547,6 +554,34 @@ void TestReadWriteWatcherInteractions() {
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
} }
void TestConcurrentSharedWatchers() {
constexpr size_t worker_count = 8;
PageManager manager;
auto *memory = Allocate(TRACKER_PAGE_SIZE);
const auto address = reinterpret_cast<uint64_t>(memory);
bool watched = false;
std::barrier phase(worker_count, [&]() noexcept {
watched = !watched;
Check(Protection(memory) == (watched ? PAGE_READONLY : PAGE_READWRITE),
"concurrent watchers lost protection or failed to restore writes");
});
std::vector<std::jthread> workers;
for (size_t i = 0; i < worker_count; i++) {
workers.emplace_back([&] {
for (size_t round = 0; round < 100; round++) {
manager.UpdatePageWatchers<true>(address, TRACKER_PAGE_SIZE);
phase.arrive_and_wait();
manager.UpdatePageWatchers<false>(address, TRACKER_PAGE_SIZE);
phase.arrive_and_wait();
}
});
}
for (auto &worker : workers) {
worker.join();
}
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
}
[[noreturn]] void RunDeathCase(const char *name) { [[noreturn]] void RunDeathCase(const char *name) {
PageManager manager; PageManager manager;
const auto page_size = manager.GetPageSize(); const auto page_size = manager.GetPageSize();
@ -669,6 +704,7 @@ int main(int argc, char **argv) {
TestRegionMaskWatcherRanges(); TestRegionMaskWatcherRanges();
TestRegionEndpointBatching(); TestRegionEndpointBatching();
TestReadWriteWatcherInteractions(); TestReadWriteWatcherInteractions();
TestConcurrentSharedWatchers();
TestFatalPaths(); TestFatalPaths();
std::puts("PageManagerTests: all cases passed"); std::puts("PageManagerTests: all cases passed");
return 0; return 0;