#include "graphics/host_gpu/memoryTracker.h" #include "graphics/host_gpu/rangeSet.h" #include "common/assert.h" #include #include #include #include #include #include #include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #include #undef min #undef max #endif namespace { using Libs::Graphics::DirtySource; using Libs::Graphics::CpuFaultAction; using Libs::Graphics::MemoryTracker; using Libs::Graphics::PageFaultAccess; using Libs::Graphics::PageFaultPhase; using Libs::Graphics::PageManager; using Libs::Graphics::PageWatchMode; using Libs::Graphics::RegionManager; using Libs::Graphics::RangeSet; void Check(bool value, const char *text) { if (!value) { std::fprintf(stderr, "MemoryTrackerTests: failed: %s\n", text); std::abort(); } } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS bool IsWritable(const void *address) { MEMORY_BASIC_INFORMATION info{}; Check(VirtualQuery(address, &info, sizeof(info)) != 0, "VirtualQuery failed"); return info.Protect == PAGE_READWRITE; } uint32_t Protection(const void *address) { MEMORY_BASIC_INFORMATION info{}; Check(VirtualQuery(address, &info, sizeof(info)) != 0, "VirtualQuery failed"); return info.Protect; } class SharedPage final { public: SharedPage(uintptr_t address, uint64_t size) { mapping_ = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, static_cast(size >> 32u), static_cast(size), nullptr); Check(mapping_ != nullptr, "CreateFileMapping failed"); guest = static_cast(MapViewOfFileEx( mapping_, FILE_MAP_ALL_ACCESS, 0, 0, size, reinterpret_cast(address))); Check(guest == reinterpret_cast(address), "fixed shared view failed"); backing = static_cast( MapViewOfFile(mapping_, FILE_MAP_ALL_ACCESS, 0, 0, size)); Check(backing != nullptr, "shared backing view failed"); } ~SharedPage() { Check(UnmapViewOfFile(backing) != 0, "shared backing unmap failed"); Check(UnmapViewOfFile(guest) != 0, "shared guest unmap failed"); Check(CloseHandle(mapping_) != 0, "shared mapping close failed"); } SharedPage(const SharedPage &) = delete; SharedPage &operator=(const SharedPage &) = delete; uint8_t *guest = nullptr; uint8_t *backing = nullptr; private: HANDLE mapping_ = nullptr; }; bool DummyFault(void *, PageFaultAccess, uint64_t, uint64_t, PageFaultPhase) noexcept { return true; } struct TrackerHarness { static bool Fault(void *context, PageFaultAccess access, uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept { auto *self = static_cast(context); if (self == nullptr || self->target == nullptr) { EXIT("memory-tracker test fault has no target\n"); } return self->discard_virtual ? self->target->InvalidateVirtualGpuWrite(access, vaddr, size, phase) : self->target->InvalidateRegion(vaddr, size, phase); } explicit TrackerHarness( PageWatchMode gpu_watch_mode = PageWatchMode::ReadWrite) : page_manager(Fault, this), tracker(page_manager, gpu_watch_mode) { target = &tracker; } MemoryTracker *target = nullptr; bool discard_virtual = false; PageManager page_manager; MemoryTracker tracker; }; struct SharedTrackerHarness { static bool Fault(void *context, PageFaultAccess, uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept { auto *self = static_cast(context); if (self == nullptr) { EXIT("shared memory-tracker test fault has no harness\n"); } const bool first = self->first.InvalidateRegion(vaddr, size, phase); const bool second = self->second.InvalidateRegion(vaddr, size, phase); return first || second; } SharedTrackerHarness() : page_manager(Fault, this), first(page_manager), second(page_manager) {} PageManager page_manager; MemoryTracker first; MemoryTracker second; }; struct SharedMetadataImageHarness { static bool Fault(void *context, PageFaultAccess access, uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept { auto *self = static_cast(context); if (self == nullptr || (access != PageFaultAccess::Read && access != PageFaultAccess::Write)) { EXIT("shared metadata/image test received an invalid fault\n"); } const bool metadata = access == PageFaultAccess::Write && self->metadata.InvalidateVirtualGpuWrite( access, vaddr, size, phase); const bool image = self->image.InvalidateRegion(vaddr, size, phase); return metadata || image; } SharedMetadataImageHarness() : page_manager(Fault, this), image(page_manager), metadata(page_manager, PageWatchMode::Write) {} PageManager page_manager; MemoryTracker image; MemoryTracker metadata; }; struct SplitTrackerHarness { static bool Fault(void *context, PageFaultAccess access, uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept { auto *self = static_cast(context); if (self == nullptr) { EXIT("split memory-tracker test fault has no harness\n"); } if (phase == PageFaultPhase::Release) { return true; } const bool buffer = self->buffer.InvalidateRegion(vaddr, size, phase); const bool metadata = self->metadata.InvalidateVirtualGpuWrite( access, vaddr, size, phase); const bool image = self->image.InvalidateRegion(vaddr, size, phase); if (static_cast(buffer) + static_cast(metadata) + static_cast(image) != 1) { EXIT("split memory-tracker fault matched multiple owners\n"); } return true; } SplitTrackerHarness() : page_manager(Fault, this), buffer(page_manager), image(page_manager), metadata(page_manager, PageWatchMode::Write) {} PageManager page_manager; MemoryTracker buffer; MemoryTracker image; MemoryTracker metadata; }; struct DownloadTrackerHarness { static bool Fault(void *context, PageFaultAccess access, uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept { auto *self = static_cast(context); if (self == nullptr) { EXIT("download memory-tracker test fault has no harness\n"); } if (phase == PageFaultPhase::Invalidate) { if (self->pending_access != PageFaultAccess::Unknown) { EXIT("download memory-tracker test has an overlapping request\n"); } const auto action = self->tracker.BeginCpuFault(vaddr, size); if (action == CpuFaultAction::Download) { self->pending_access = access; } return action != CpuFaultAction::Untracked; } if (phase == PageFaultPhase::Release) { return true; } const bool downloaded = self->pending_access != PageFaultAccess::Unknown; if (downloaded) { if (self->pending_access != access || self->download_data.empty()) { EXIT("download memory-tracker test has invalid completion state\n"); } if (self->backing == nullptr || self->download_address < self->guest_address) { EXIT("download memory-tracker test has no backing alias\n"); } std::memcpy(self->backing + self->download_address - self->guest_address, self->download_data.data(), self->download_data.size()); } const bool completed = self->tracker.CompleteCpuFault(vaddr, size, access, downloaded); self->pending_access = PageFaultAccess::Unknown; return completed; } DownloadTrackerHarness() : page_manager(Fault, this), tracker(page_manager) {} PageFaultAccess pending_access = PageFaultAccess::Unknown; uint64_t download_address = 0; uint64_t guest_address = 0; uint8_t *backing = nullptr; std::vector download_data; PageManager page_manager; MemoryTracker tracker; }; std::atomic g_native_page_manager{nullptr}; std::atomic_bool g_native_fault_entered{false}; std::atomic_bool g_unmap_contended{false}; void UnmapContended() noexcept { g_unmap_contended.store(true, std::memory_order_release); } LONG CALLBACK NativeTrackerFaultHandler(EXCEPTION_POINTERS *exception) { if (exception == nullptr || exception->ExceptionRecord == nullptr || exception->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) { return EXCEPTION_CONTINUE_SEARCH; } const auto operation = exception->ExceptionRecord->ExceptionInformation[0]; const auto access = operation == 0 ? PageFaultAccess::Read : operation == 1 ? PageFaultAccess::Write : operation == 8 ? PageFaultAccess::Execute : PageFaultAccess::Unknown; auto *page_manager = g_native_page_manager.load(std::memory_order_acquire); if (page_manager == nullptr) { return EXCEPTION_CONTINUE_SEARCH; } g_native_fault_entered.store(true, std::memory_order_release); return page_manager->HandleFault( access, exception->ExceptionRecord->ExceptionInformation[1]) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; } void TestPendingFaultBlocksUploadConsumption() { constexpr uintptr_t base = 0x0000000200010000ull; constexpr uint64_t region_size = 4ull * 1024ull * 1024ull; PageManager page_manager(DummyFault, nullptr); const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size); RegionManager region(page_manager, address & ~(region_size - 1)); Libs::Graphics::RegionBits changed; { std::scoped_lock lock(region.lock); region.Track(address, page_size); changed = region.ForEachModifiedRange( address, page_size, [](uint64_t, uint64_t) noexcept {}); changed = region.ChangeState(address, page_size); Check(region.BeginCpuFault(address, 1) == CpuFaultAction::Continue, "fault ownership rejected an already CPU-dirty page"); } page_manager.UpdatePageWatchers(false, address, page_size); uint32_t ranges = 0; { std::scoped_lock lock(region.lock); const auto pending_change = region.ForEachModifiedRange( address, page_size, [&](uint64_t, uint64_t) noexcept { ranges++; }); Check(ranges == 0 && pending_change.none() && region.IsModified(address - region.GetCpuAddr(), page_size), "pending fault page was consumed by upload"); Check(region.CompleteCpuFault(address, 1, PageFaultAccess::Write, false), "fault completion was not recorded"); changed = region.ForEachModifiedRange( address, page_size, [&](uint64_t, uint64_t) noexcept { ranges++; }); } Check(ranges == 1, "completed fault page was not available to upload"); { std::scoped_lock lock(region.lock); changed = region.ChangeState(address, page_size); } region.ApplyProtection(changed, false); page_manager.OnGpuUnmap(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestCleanReadFaultPreservesCpuState() { constexpr uintptr_t base = 0x0000000200010000ull; PageManager page_manager(DummyFault, nullptr); MemoryTracker tracker(page_manager); const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast(VirtualAlloc( reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size); tracker.ForEachUploadRange(address, page_size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(!tracker.IsRegionCpuModified(address, page_size) && !tracker.IsRegionGpuModified(address, page_size), "clean read-fault setup retained dirty ownership"); Check(tracker.BeginCpuFault(address, 1, PageFaultAccess::Read) == CpuFaultAction::Continue, "clean tracked read fault was not accepted"); Check(tracker.CompleteCpuFault(address, 1, PageFaultAccess::Read, false), "clean tracked read fault did not complete"); Check(!tracker.IsRegionCpuModified(address, page_size) && !tracker.IsRegionGpuModified(address, page_size), "clean read fault incorrectly transferred write ownership to the CPU"); tracker.UntrackMemory(address, page_size); page_manager.OnGpuUnmap(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestGpuDownloadFaultOwnership() { constexpr uintptr_t base = 0x0000000200010000ull; DownloadTrackerHarness harness; const auto page_size = harness.page_manager.GetPageSize(); SharedPage shared(base, page_size); auto *memory = shared.guest; const auto address = reinterpret_cast(memory); harness.guest_address = address; harness.backing = shared.backing; harness.page_manager.OnGpuMap(address, page_size); harness.tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); harness.download_address = address + 32; harness.download_data = {0x11, 0x22, 0x33, 0x44}; Check(harness.page_manager.HandleFault(PageFaultAccess::Read, address + 32), "GPU-dirty read fault was not handled"); Check(std::memcmp(memory + 32, harness.download_data.data(), 4) == 0 && !harness.tracker.IsRegionGpuModified(address, page_size) && !harness.tracker.IsRegionCpuModified(address, page_size) && Protection(memory) == PAGE_READONLY && harness.page_manager.IsTracked(address), "GPU readback did not leave a clean write-watched page"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + 32), "write watcher was not preserved after GPU readback"); Check(harness.tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "post-read CPU write did not claim CPU ownership"); harness.tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); harness.download_data = {0xaa, 0xbb, 0xcc, 0xdd}; Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + 33), "GPU-dirty write fault was not handled"); Check(std::memcmp(memory + 32, harness.download_data.data(), 4) == 0 && !harness.tracker.IsRegionGpuModified(address, page_size) && harness.tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "GPU write fault did not download before granting CPU ownership"); harness.tracker.UnmapMemory(address, page_size); } void TestVirtualGpuWriteDiscard() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness(PageWatchMode::Write); harness.discard_virtual = true; const auto page_size = harness.page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); memory[8] = 0x5a; harness.page_manager.OnGpuMap(address, page_size); harness.tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(Protection(memory) == PAGE_READONLY && memory[8] == 0x5a && harness.tracker.IsRegionGpuModified(address, page_size), "virtual GPU ownership did not preserve authoritative backing reads"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + 8), "virtual GPU write fault was not discarded"); Check(!harness.tracker.IsRegionGpuModified(address, page_size) && harness.tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "virtual GPU discard did not transfer the page to CPU ownership"); harness.tracker.UnmapMemory(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestSameSlabTrackerArbitration() { constexpr uintptr_t base = 0x0000000200010000ull; SplitTrackerHarness harness; const auto page_size = harness.page_manager.GetPageSize(); auto *memory = static_cast(VirtualAlloc( reinterpret_cast(base), page_size * 3, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); harness.page_manager.OnGpuMap(address, page_size * 3); harness.buffer.ForEachUploadRange( address, page_size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); harness.image.ForEachUploadRange( address + page_size, page_size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); harness.metadata.ForEachUploadRange( address + page_size * 2, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(harness.buffer.BeginCpuFault(address + page_size * 2, 1) == CpuFaultAction::Untracked && harness.image.BeginCpuFault(address + page_size * 2, 1) == CpuFaultAction::Untracked, "unrelated same-slab trackers claimed the metadata page"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + page_size * 2 + 8), "metadata write fault was not exclusively handled"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + 8), "buffer write fault was not exclusively handled"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + page_size + 8), "image write fault was not exclusively handled"); Check(harness.buffer.IsRegionCpuModified(address, page_size) && harness.image.IsRegionCpuModified(address + page_size, page_size) && harness.metadata.IsRegionCpuModified(address + page_size * 2, page_size) && IsWritable(memory) && IsWritable(memory + page_size) && IsWritable(memory + page_size * 2), "exclusive same-slab faults did not transfer exact CPU ownership"); harness.buffer.UntrackMemory(address, page_size); harness.image.UntrackMemory(address + page_size, page_size); harness.metadata.UntrackMemory(address + page_size * 2, page_size); harness.page_manager.OnGpuUnmap(address, page_size * 3); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestSharedMetadataAndImagePageFault() { constexpr uintptr_t base = 0x0000000200010000ull; SharedMetadataImageHarness harness; const auto page_size = harness.page_manager.GetPageSize(); auto *memory = static_cast(VirtualAlloc( reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); harness.page_manager.OnGpuMap(address, page_size); harness.metadata.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); harness.image.ForEachUploadRange( address, page_size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(Protection(memory) == PAGE_READONLY && harness.metadata.IsRegionGpuModified(address, page_size) && !harness.image.IsRegionCpuModified(address, page_size), "shared metadata/image page was not write-watched"); Check(harness.page_manager.HandleFault(PageFaultAccess::Write, address + 8), "shared metadata/image write fault was not handled"); Check(!harness.metadata.IsRegionGpuModified(address, page_size) && harness.metadata.IsRegionCpuModified(address, page_size) && harness.image.IsRegionCpuModified(address, page_size) && IsWritable(memory), "shared write fault did not invalidate both native trackers"); harness.metadata.UntrackMemory(address, page_size); harness.image.UntrackMemory(address, page_size); harness.page_manager.OnGpuUnmap(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestRangeSet() { RangeSet ranges; ranges.Add(0x1000, 0x80); ranges.Add(0x1080, 0x80); ranges.Add(0x1200, 0x40); auto intersections = ranges.Intersections(0x1070, 0x1b0); Check(intersections.size() == 2 && intersections[0].address == 0x1070 && intersections[0].size == 0x90 && intersections[1].address == 0x1200 && intersections[1].size == 0x20, "range set did not merge and intersect exact byte ranges"); ranges.Subtract(0x1040, 0x1e0); intersections = ranges.Intersections(0x1000, 0x300); Check(intersections.size() == 2 && intersections[0].address == 0x1000 && intersections[0].size == 0x40 && intersections[1].address == 0x1220 && intersections[1].size == 0x20, "range set subtraction did not preserve both exact tails"); } void TestCpuDirtyUploadAndFault() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size * 2); Check(tracker.IsRegionCpuModified(address + 16, 32), "new region was not CPU dirty"); uint32_t ranges = 0; bool uploaded = false; tracker.ForEachUploadRange( address + 16, 32, false, [&](uint64_t upload_addr, uint64_t upload_size) noexcept { Check(upload_addr == address && upload_size == page_size, "upload range was not page aligned"); ranges++; }, [&]() noexcept { uploaded = true; }); Check(ranges == 1 && uploaded && !tracker.IsRegionCpuModified(address + 16, 32) && !IsWritable(memory), "upload did not clear CPU dirty state and arm protection"); Check(page_manager.HandleFault(PageFaultAccess::Write, address + 24), "tracked CPU write fault was not handled"); Check(tracker.IsRegionCpuModified(address + 16, 32) && IsWritable(memory), "fault did not restore CPU dirty state and write access"); tracker.ForEachUploadRange( address + 16, 32, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(!tracker.IsRegionCpuModified(address + 16, 32) && !IsWritable(memory), "fault owner state did not support a balanced rearm"); tracker.MarkRegionAsCpuModified(address + 16, 32); Check(IsWritable(memory), "explicit CPU dirty transition did not release the rearmed watch"); tracker.UnmapMemory(address, page_size * 2); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestFaultDuringUploadRemainsDirty() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size); tracker.ForEachUploadRange( address, page_size, false, [](uint64_t, uint64_t) noexcept {}, [&]() noexcept { bool handled = false; std::thread fault([&] { handled = page_manager.HandleFault(PageFaultAccess::Write, address); }); fault.join(); Check(handled, "concurrent write racing upload was not handled"); }); Check(tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "upload completion erased a racing CPU dirty transition"); tracker.UnmapMemory(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestNativeStoreDuringRangeEnumeration() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size); void *handler = AddVectoredExceptionHandler(1, NativeTrackerFaultHandler); Check(handler != nullptr, "AddVectoredExceptionHandler failed"); Check(g_native_page_manager.exchange(&page_manager, std::memory_order_acq_rel) == nullptr, "native page manager already installed"); g_native_fault_entered.store(false, std::memory_order_release); std::thread writer; tracker.ForEachUploadRange( address, page_size, false, [&](uint64_t, uint64_t) noexcept { writer = std::thread( [&] { *static_cast(memory) = 0x6b; }); while (!g_native_fault_entered.load(std::memory_order_acquire)) { std::this_thread::yield(); } }, [&]() noexcept { writer.join(); }); Check(g_native_page_manager.exchange(nullptr, std::memory_order_acq_rel) == &page_manager, "native page manager publication changed"); Check(RemoveVectoredExceptionHandler(handler) != 0, "RemoveVectoredExceptionHandler failed"); Check(memory[0] == 0x6b && tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "native store during range enumeration was lost"); tracker.UnmapMemory(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestFaultDuringDownloadSynchronization() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); SharedPage shared(base, page_size * 3); auto *memory = shared.guest; const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size * 3); tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); tracker.ForEachUploadRange( address + page_size * 2, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); memory[page_size] = 0x31; void *handler = AddVectoredExceptionHandler(1, NativeTrackerFaultHandler); Check(handler != nullptr, "AddVectoredExceptionHandler failed"); Check(g_native_page_manager.exchange(&page_manager, std::memory_order_acq_rel) == nullptr, "native page manager already installed"); g_native_fault_entered.store(false, std::memory_order_release); uint32_t ranges = 0; std::vector download(page_size, 0x5a); std::thread writer; { PageManager::BackingWrite first(page_manager, address, page_size); PageManager::BackingWrite third(page_manager, address + page_size * 2, page_size); tracker.ForEachDownloadRange( address, page_size * 3, [&](uint64_t download_address, uint64_t download_size) noexcept { Check((download_address == address || download_address == address + page_size * 2) && download_size == page_size, "download transaction reported the wrong range"); ranges++; if (download_address == address) { writer = std::thread( [&] { *static_cast(memory) = 0x7c; }); while (!g_native_fault_entered.load(std::memory_order_acquire)) { std::this_thread::yield(); } } std::memcpy(shared.backing + download_address - address, download.data(), download_size); Check(Protection(reinterpret_cast(download_address)) == PAGE_NOACCESS, "backing alias exposed the protected guest page"); Check(Protection(memory + page_size) == PAGE_READWRITE, "clean middle page was reserved or protected"); }); Check(ranges == 2 && Protection(memory) == PAGE_NOACCESS && Protection(memory + page_size) == PAGE_READWRITE && Protection(memory + page_size * 2) == PAGE_NOACCESS, "download completion released protection before reservation"); } writer.join(); Check(g_native_page_manager.exchange(nullptr, std::memory_order_acq_rel) == &page_manager, "native page manager publication changed"); Check(RemoveVectoredExceptionHandler(handler) != 0, "RemoveVectoredExceptionHandler failed"); Check(ranges == 2, "partial download did not enumerate both dirty ranges"); Check(memory[0] == 0x7c && memory[1] == 0x5a && memory[page_size] == 0x31 && memory[page_size * 2] == 0x5a, "reserved backing write raced or lost downloaded data"); Check(!tracker.IsRegionGpuModified(address, page_size * 3) && tracker.IsRegionCpuModified(address, page_size * 3), "partial download left incorrect tracker ownership"); Check(IsWritable(memory), "first dirty page did not restore write access"); Check(IsWritable(memory + page_size), "clean page lost write access"); Check(Protection(memory + page_size * 2) == PAGE_READONLY && page_manager.IsTracked(address + page_size * 2), "uncontended dirty page did not retain its clean write watch"); tracker.UnmapMemory(address, page_size * 3); } void TestFaultAndExplicitDirtyRace() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size); for (uint32_t iteration = 0; iteration < 64; iteration++) { tracker.ForEachUploadRange( address, page_size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); std::atomic_bool start{false}; bool handled = false; std::thread fault([&] { while (!start.load(std::memory_order_acquire)) { std::this_thread::yield(); } handled = page_manager.HandleFault(PageFaultAccess::Write, address); }); std::thread dirty([&] { while (!start.load(std::memory_order_acquire)) { std::this_thread::yield(); } tracker.MarkRegionAsCpuModified(address, page_size); }); start.store(true, std::memory_order_release); fault.join(); dirty.join(); Check(handled && tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory), "fault/explicit-dirty race lost dirty state or write access"); } tracker.UnmapMemory(address, page_size); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestSharedTrackersAndConcurrentPageFaults() { constexpr uintptr_t base = 0x0000000200010000ull; SharedTrackerHarness harness; const auto page_size = harness.page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); harness.page_manager.OnGpuMap(address, page_size * 2); for (auto *tracker : {&harness.first, &harness.second}) { tracker->ForEachUploadRange( address, page_size * 2, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); } Check(!IsWritable(memory) && !IsWritable(memory + page_size), "shared trackers did not arm both pages"); std::atomic_bool start{false}; bool first_handled = false; bool second_handled = false; std::thread first_fault([&] { while (!start.load(std::memory_order_acquire)) { std::this_thread::yield(); } first_handled = harness.page_manager.HandleFault(PageFaultAccess::Write, address + 8); }); std::thread second_fault([&] { while (!start.load(std::memory_order_acquire)) { std::this_thread::yield(); } second_handled = harness.page_manager.HandleFault( PageFaultAccess::Write, address + page_size + 8); }); start.store(true, std::memory_order_release); first_fault.join(); second_fault.join(); Check(first_handled && second_handled, "concurrent faults were not handled by shared trackers"); for (auto *tracker : {&harness.first, &harness.second}) { Check(tracker->IsRegionCpuModified(address, page_size * 2), "a shared tracker lost concurrent CPU dirtiness"); } harness.first.UntrackMemory(address, page_size * 2); harness.second.UnmapMemory(address, page_size * 2); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestGpuDirtyBits() { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), page_size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, page_size * 2); tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(tracker.IsRegionGpuModified(address, page_size) && !tracker.IsRegionGpuModified(address + page_size, page_size) && Protection(memory) == PAGE_NOACCESS, "GPU dirty state escaped the requested range"); tracker.UnmarkRegionAsGpuModified(address, page_size); Check(!tracker.IsRegionGpuModified(address, page_size) && Protection(memory) == PAGE_READONLY, "GPU dirty state did not restore write-only tracking"); tracker.MarkRegionAsGpuModified(address, page_size); Check(tracker.IsRegionGpuModified(address, page_size) && Protection(memory) == PAGE_NOACCESS, "explicit GPU dirty transition did not trap CPU access"); tracker.UnmarkRegionAsGpuModified(address, page_size); tracker.MarkRegionAsCpuModified(address, page_size); tracker.UnmapMemory(address, page_size * 2); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } void TestCrossRegionUpload() { constexpr uintptr_t base = 0x0000000200010000ull; constexpr uint64_t region_size = 4ull * 1024ull * 1024ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), region_size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); const auto boundary = (address + region_size - 1) & ~(region_size - 1); page_manager.OnGpuMap(address, region_size * 2); uint32_t ranges = 0; tracker.ForEachUploadRange( boundary - page_size, page_size * 2, false, [&](uint64_t, uint64_t) noexcept { ranges++; }, []() noexcept {}); Check(ranges == 2 && !tracker.IsRegionCpuModified(boundary - page_size, page_size * 2) && !IsWritable(reinterpret_cast(boundary - page_size)) && !IsWritable(reinterpret_cast(boundary)), "cross-region upload did not clear and protect both regions"); tracker.MarkRegionAsCpuModified(boundary - page_size, page_size * 2); tracker.ForEachUploadRange( boundary - page_size, page_size * 2, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); Check(tracker.IsRegionGpuModified(boundary - page_size, page_size * 2), "cross-region written upload did not mark GPU dirty state"); tracker.UnmarkRegionAsGpuModified(boundary - page_size, page_size * 2); tracker.MarkRegionAsCpuModified(boundary - page_size, page_size * 2); tracker.UnmapMemory(address, region_size * 2); Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } [[noreturn]] void RunDeathCase(const char *name) { constexpr uintptr_t base = 0x0000000200010000ull; TrackerHarness harness; auto &tracker = harness.tracker; auto &page_manager = harness.page_manager; const auto page_size = page_manager.GetPageSize(); if (std::strcmp(name, "unmapped") == 0) { (void)tracker.IsRegionCpuModified(base, page_size); } const auto allocation_size = std::strcmp(name, "missing-download-bytes") == 0 ? page_size * 2 : page_size; auto *memory = static_cast( VirtualAlloc(reinterpret_cast(base), allocation_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Check(memory == reinterpret_cast(base), "fixed VirtualAlloc failed"); const auto address = reinterpret_cast(memory); page_manager.OnGpuMap(address, allocation_size); if (std::strcmp(name, "gpu-dirty-fault") == 0 || std::strcmp(name, "gpu-dirty-read") == 0 || std::strcmp(name, "gpu-dirty-explicit-cpu") == 0 || std::strcmp(name, "virtual-gpu-read") == 0) { harness.discard_virtual = std::strcmp(name, "virtual-gpu-read") == 0; tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); if (std::strcmp(name, "gpu-dirty-explicit-cpu") == 0) { tracker.MarkRegionAsCpuModified(address, page_size); } else { (void)page_manager.HandleFault( (std::strcmp(name, "gpu-dirty-read") == 0 || std::strcmp(name, "virtual-gpu-read") == 0) ? PageFaultAccess::Read : PageFaultAccess::Write, address); } } else if (std::strcmp(name, "reentrant-upload") == 0) { tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, [&]() noexcept { (void)tracker.IsRegionCpuModified(address, page_size); }); } else if (std::strcmp(name, "writable-upload-race") == 0) { std::atomic_bool start{false}; std::atomic_bool entered{false}; std::thread fault([&] { while (!start.load(std::memory_order_acquire)) { std::this_thread::yield(); } entered.store(true, std::memory_order_release); (void)page_manager.HandleFault(PageFaultAccess::Write, address); }); tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, [&]() noexcept { start.store(true, std::memory_order_release); while (!entered.load(std::memory_order_acquire)) { std::this_thread::yield(); } }); fault.join(); } else if (std::strcmp(name, "gpu-dirty-unmap-race") == 0) { g_unmap_contended.store(false, std::memory_order_release); MemoryTracker::SetUnmapContentionHook(UnmapContended); std::thread unmap; tracker.ForEachUploadRange( address, page_size, true, [](uint64_t, uint64_t) noexcept {}, [&]() noexcept { unmap = std::thread( [&] { tracker.UnmapMemory(address, page_size); }); while (!g_unmap_contended.load(std::memory_order_acquire)) { std::this_thread::yield(); } }); unmap.join(); } else if (std::strcmp(name, "missing-download-bytes") == 0) { tracker.ForEachUploadRange( address, allocation_size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {}); RangeSet dirty_bytes; dirty_bytes.Add(address, 1); PageManager::BackingWrite backing(page_manager, address, page_size); tracker.ForEachDownloadRange( address, allocation_size, [&](uint64_t dirty_address, uint64_t dirty_size) noexcept { for (auto page = dirty_address; page < dirty_address + dirty_size; page += page_size) { if (dirty_bytes.Intersections(page, page_size).empty()) { EXIT("GPU-dirty test page has no dirty byte record\n"); } } }, [](uint64_t, uint64_t) noexcept {}); } std::_Exit(0x7f); } void TestFatalPaths() { char path[MAX_PATH]{}; Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed"); for (const char *name : {"gpu-dirty-fault", "gpu-dirty-read", "virtual-gpu-read", "gpu-dirty-explicit-cpu", "unmapped", "reentrant-upload", "writable-upload-race", "gpu-dirty-unmap-race", "missing-download-bytes"}) { std::string command = std::string("\"") + path + "\" --death " + name; std::vector mutable_command(command.begin(), command.end()); mutable_command.push_back('\0'); STARTUPINFOA startup{sizeof(startup)}; PROCESS_INFORMATION process{}; Check(CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process) != 0, "CreateProcess failed"); Check(WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0, "MemoryTracker death test timed out"); DWORD exit_code = 0; Check(GetExitCodeProcess(process.hProcess, &exit_code) != 0 && (exit_code == 321 || exit_code == EXCEPTION_NONCONTINUABLE_EXCEPTION), "MemoryTracker death path used the wrong exit"); CloseHandle(process.hThread); CloseHandle(process.hProcess); } } #endif } // namespace int main(int argc, char **argv) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (argc == 3 && std::strcmp(argv[1], "--death") == 0) { RunDeathCase(argv[2]); } TestCpuDirtyUploadAndFault(); TestPendingFaultBlocksUploadConsumption(); TestCleanReadFaultPreservesCpuState(); TestGpuDownloadFaultOwnership(); TestVirtualGpuWriteDiscard(); TestSameSlabTrackerArbitration(); TestSharedMetadataAndImagePageFault(); TestRangeSet(); TestGpuDirtyBits(); TestCrossRegionUpload(); TestFaultDuringUploadRemainsDirty(); TestNativeStoreDuringRangeEnumeration(); TestFaultDuringDownloadSynchronization(); TestFaultAndExplicitDirtyRace(); TestSharedTrackersAndConcurrentPageFaults(); TestFatalPaths(); std::puts("MemoryTrackerTests: all cases passed"); return 0; #else (void)argc; (void)argv; std::fputs("MemoryTrackerTests: unsupported platform\n", stderr); return 1; #endif }