#include "graphics/host_gpu/memoryTracker.h" #include "common/alignment.h" #include "common/assert.h" namespace Libs::Graphics { static_assert(std::atomic::is_always_lock_free); MemoryTracker::MemoryTracker(PageManager& page_manager): m_page_manager(page_manager) { m_regions = std::make_unique[]>(REGION_COUNT); } MemoryTracker::~MemoryTracker() = default; #if KYTY_BUILD == KYTY_BUILD_DEBUG void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size, const char* operation) const noexcept { if (!GuestRange {vaddr, size}.Valid() || (vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 || (size & (TRACKER_PAGE_SIZE - 1)) != 0) { EXIT("MemoryTracker: invalid dirty-page validation range\n"); } for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) { if (!dirty.Intersects(page, TRACKER_PAGE_SIZE)) { EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s " "addr=0x%016" PRIx64 "\n", operation, page); } } } void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size, const char* operation) { ValidateRange(vaddr, size); const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE); const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE); for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) { const bool has_dirty_bytes = dirty.Intersects(page, TRACKER_PAGE_SIZE); if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) { EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s " "addr=0x%016" PRIx64 "\n", operation, page); } } } #endif void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) { if (!GuestRange {vaddr, size}.Valid()) { EXIT("invalid memory tracker range\n"); } } RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) { if (auto* manager = m_regions[index].load(std::memory_order_acquire); manager != nullptr) { return manager; } std::lock_guard lock(m_region_mutex); if (auto* manager = m_regions[index].load(std::memory_order_acquire); manager != nullptr) { return manager; } auto manager = std::make_unique(m_page_manager, index * TRACKER_REGION_SIZE); auto* ptr = manager.get(); m_region_storage.push_back(std::move(manager)); m_regions[index].store(ptr, std::memory_order_release); return ptr; } void MemoryTracker::QueueBdaSync(uint64_t vaddr, uint64_t size) { const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE); const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE); std::lock_guard lock(m_bda_sync_mutex); m_bda_sync.Add(begin, end - begin); } RangeSet MemoryTracker::TakeBdaSync() { std::lock_guard lock(m_bda_sync_mutex); return std::exchange(m_bda_sync, RangeSet {}); } bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); return Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { std::scoped_lock lock(manager->lock); return manager->IsModified(offset, bytes); }); } bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); return Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { std::scoped_lock lock(manager->lock); return manager->IsModified(offset, bytes); }); } void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { std::scoped_lock lock(manager->lock); manager->ChangeState(manager->GetCpuAddr() + offset, bytes); }); QueueBdaSync(vaddr, size); } void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { std::scoped_lock lock(manager->lock); manager->ChangeState(manager->GetCpuAddr() + offset, bytes); }); } void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { std::scoped_lock lock(manager->lock); manager->ChangeState(manager->GetCpuAddr() + offset, bytes); }); } void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) { CheckNotInUploadCallback(); std::vector managers; managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) / TRACKER_REGION_SIZE); Iterate(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) { managers.push_back(manager); }); std::vector> locks; locks.reserve(managers.size()); for (auto* manager: managers) { locks.emplace_back(manager->lock); } if (Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { return manager->IsModified(offset, bytes); })) { EXIT("cannot untrack GPU-dirty memory\n"); } Iterate(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { manager->ChangeState(manager->GetCpuAddr() + offset, bytes); }); locks.clear(); QueueBdaSync(vaddr, size); } } // namespace Libs::Graphics