mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 06:16:55 +01:00
renderer: remove legacy code left from refactoring
This commit is contained in:
@@ -157,7 +157,6 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
}
|
||||
@@ -168,93 +167,4 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
UntrackMemoryLocked(vaddr, size);
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateRegion(uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
switch (action) {
|
||||
case CpuFaultAction::Untracked: return false;
|
||||
case CpuFaultAction::Continue: return true;
|
||||
case CpuFaultAction::Download:
|
||||
EXIT("generic region invalidation cannot download GPU-dirty memory\n");
|
||||
}
|
||||
}
|
||||
case PageFaultPhase::Complete:
|
||||
return CompleteCpuFault(vaddr, size, PageFaultAccess::Write, false);
|
||||
}
|
||||
EXIT("unsupported region invalidation phase\n");
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const bool gpu_modified = Iterate<false>(
|
||||
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
});
|
||||
if (!gpu_modified) {
|
||||
return false;
|
||||
}
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
if (access != PageFaultAccess::Write || action != CpuFaultAction::Download) {
|
||||
EXIT("virtual GPU write fault requires write access to GPU-dirty memory\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PageFaultPhase::Complete: {
|
||||
if (access != PageFaultAccess::Write) {
|
||||
EXIT("virtual GPU write completion requires write access\n");
|
||||
}
|
||||
bool completed = false;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&completed](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (completed) {
|
||||
EXIT("virtual GPU write fault spans multiple tracked regions\n");
|
||||
}
|
||||
completed =
|
||||
manager->CompleteVirtualGpuWrite(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
EXIT("unsupported virtual GPU write invalidation phase\n");
|
||||
}
|
||||
|
||||
CpuFaultAction MemoryTracker::BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
CpuFaultAction action = CpuFaultAction::Untracked;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&action, access](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (action != CpuFaultAction::Untracked) {
|
||||
EXIT("CPU fault spans multiple tracked regions\n");
|
||||
}
|
||||
action = manager->BeginCpuFault(manager->GetCpuAddr() + offset, bytes, access);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
bool MemoryTracker::CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
bool found = false;
|
||||
Iterate<false>(
|
||||
vaddr, size,
|
||||
[&found, access, downloaded](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (found) {
|
||||
EXIT("CPU fault completion spans multiple tracked regions\n");
|
||||
}
|
||||
found = manager->CompleteCpuFault(manager->GetCpuAddr() + offset, bytes, access,
|
||||
downloaded);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -30,13 +30,6 @@ public:
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] CpuFaultAction
|
||||
BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) noexcept;
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept;
|
||||
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
template <typename Flush>
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
|
||||
static_assert(std::is_invocable_v<Flush&>);
|
||||
@@ -78,8 +71,6 @@ public:
|
||||
EXIT("memory invalidation retained GPU-owned pages\n");
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept;
|
||||
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
@@ -102,9 +93,6 @@ public:
|
||||
}
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
if (manager->HasPendingFault(address, bytes)) {
|
||||
EXIT("GPU download synchronization raced a pending CPU fault\n");
|
||||
}
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
|
||||
preflight);
|
||||
});
|
||||
@@ -141,7 +129,6 @@ public:
|
||||
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->lock.lock();
|
||||
manager->Track(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
|
||||
bytes, range_func);
|
||||
if (!is_written) {
|
||||
|
||||
@@ -56,8 +56,6 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
|
||||
// Zero is the unknown protection sentinel.
|
||||
constexpr uint32_t UNKNOWN_PROTECTION = 0;
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
|
||||
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
|
||||
std::fputs("PageManager fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "invalid page state", stderr);
|
||||
@@ -164,9 +162,6 @@ struct PageManager::Impl {
|
||||
// Shadow the protection applied through Protect().
|
||||
uint32_t current_protection = UNKNOWN_PROTECTION;
|
||||
bool resolving = false;
|
||||
bool resolving_read_write = false;
|
||||
bool late_read_pending = false;
|
||||
bool late_write_pending = false;
|
||||
};
|
||||
|
||||
struct Region {
|
||||
@@ -193,10 +188,7 @@ struct PageManager::Impl {
|
||||
std::span<PageState*> m_pages;
|
||||
};
|
||||
|
||||
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) {
|
||||
if (fault_handler == nullptr) {
|
||||
Fatal("null page-manager fault callback");
|
||||
}
|
||||
Impl() {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info {};
|
||||
GetSystemInfo(&info);
|
||||
@@ -268,17 +260,6 @@ struct PageManager::Impl {
|
||||
return page.original_protection;
|
||||
}
|
||||
|
||||
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
|
||||
uint32_t new_protection) {
|
||||
if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) {
|
||||
page.late_read_pending = true;
|
||||
}
|
||||
if ((old_protection == NO_ACCESS_PROTECTION || old_protection == READ_ONLY_PROTECTION) &&
|
||||
new_protection == READ_WRITE_PROTECTION) {
|
||||
page.late_write_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeProtection(std::span<PageState*> pages) {
|
||||
for (auto* page: pages) {
|
||||
page->original_protection = READ_WRITE_PROTECTION;
|
||||
@@ -286,19 +267,8 @@ struct PageManager::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
static bool AllowsAccess(const PageState& page, [[maybe_unused]] uint64_t vaddr,
|
||||
PageFaultAccess access) noexcept {
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read:
|
||||
return page.current_protection == READ_ONLY_PROTECTION ||
|
||||
page.current_protection == READ_WRITE_PROTECTION;
|
||||
case PageFaultAccess::Write: return page.current_protection == READ_WRITE_PROTECTION;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtectRange(std::span<PageState*> pages, uint64_t vaddr, uint32_t protection,
|
||||
std::span<const uint32_t> expected_old, bool fault_path) noexcept {
|
||||
std::span<const uint32_t> expected_old) noexcept {
|
||||
const auto size = pages.size() * PAGE_SIZE;
|
||||
if (pages.size() != expected_old.size()) {
|
||||
FailFast("protection range state size mismatch");
|
||||
@@ -306,9 +276,6 @@ struct PageManager::Impl {
|
||||
for (size_t i = 0; i < pages.size(); i++) {
|
||||
const auto actual = pages[i]->current_protection;
|
||||
if (actual != UNKNOWN_PROTECTION && actual != expected_old[i]) {
|
||||
if (fault_path) {
|
||||
FailFast("mprotect fault transition did not match expected protection");
|
||||
}
|
||||
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
|
||||
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
|
||||
vaddr + i * PAGE_SIZE, actual, expected_old[i], protection);
|
||||
@@ -316,9 +283,6 @@ struct PageManager::Impl {
|
||||
}
|
||||
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size,
|
||||
ToMemoryMode(protection))) {
|
||||
if (fault_path) {
|
||||
FailFast("address-space fault protection transition failed");
|
||||
}
|
||||
Fatal("address-space protection failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr,
|
||||
protection);
|
||||
}
|
||||
@@ -327,47 +291,28 @@ struct PageManager::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
void Protect(PageState& page, uint64_t vaddr, uint32_t protection, uint32_t expected_old,
|
||||
bool fault_path) noexcept {
|
||||
void Protect(PageState& page, uint64_t vaddr, uint32_t protection,
|
||||
uint32_t expected_old) noexcept {
|
||||
PageState* pages[] = {&page};
|
||||
uint32_t expected[] = {expected_old};
|
||||
ProtectRange(pages, vaddr, protection, expected, fault_path);
|
||||
ProtectRange(pages, vaddr, protection, expected);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::atomic<Region*>[]> regions;
|
||||
std::vector<std::unique_ptr<Region>> region_storage;
|
||||
std::mutex region_mutex;
|
||||
PageFaultHandler fault_handler = nullptr;
|
||||
void* fault_context = nullptr;
|
||||
};
|
||||
|
||||
static_assert(std::atomic<void*>::is_always_lock_free);
|
||||
|
||||
PageManager::PageManager(PageFaultHandler fault_handler, void* fault_context)
|
||||
: m_impl(std::make_unique<Impl>(fault_handler, fault_context)) {}
|
||||
PageManager::PageManager(): m_impl(std::make_unique<Impl>()) {}
|
||||
|
||||
PageManager::~PageManager() = default;
|
||||
|
||||
uint64_t PageManager::GetPageSize() const {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("nested page fault while resolving a watched page");
|
||||
}
|
||||
return PAGE_SIZE;
|
||||
}
|
||||
|
||||
bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("IsTracked called during fault resolution");
|
||||
}
|
||||
auto* region = m_impl->FindRegion(vaddr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
return page.write_watchers != 0 || page.access_watchers != 0;
|
||||
}
|
||||
|
||||
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode) {
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
@@ -473,27 +418,14 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
}
|
||||
m_impl->ProtectRange(std::span {pages}.subspan(first, last - first),
|
||||
chunk_begin + first * PAGE_SIZE, protection,
|
||||
std::span {old_protections}.subspan(first, last - first), false);
|
||||
std::span {old_protections}.subspan(first, last - first));
|
||||
first = current;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < page_count; i++) {
|
||||
auto& page = *pages[i];
|
||||
const auto protection = new_protections[i];
|
||||
if (track) {
|
||||
switch (protection) {
|
||||
case NO_ACCESS_PROTECTION:
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
break;
|
||||
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
|
||||
default: break;
|
||||
}
|
||||
} else if (page.backing_writer == 0) {
|
||||
Impl::PublishDelayedFaults(page, old_protections[i], protection);
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
for (auto* page: pages) {
|
||||
if (!track && page->backing_writer == 0 && page->write_watchers == 0 &&
|
||||
page->access_watchers == 0) {
|
||||
page->original_protection = 0;
|
||||
}
|
||||
}
|
||||
chunk_begin = chunk_end;
|
||||
@@ -546,9 +478,6 @@ PageManager::ReserveBackingWrites(std::span<const RangeSet::Range> ranges) {
|
||||
}
|
||||
|
||||
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write began during fault resolution");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
const auto writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
@@ -562,15 +491,11 @@ void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
Fatal("backing write races page resolution at 0x%016" PRIx64, address);
|
||||
}
|
||||
page.resolving = true;
|
||||
page.resolving_read_write = true;
|
||||
page.backing_writer = writer;
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write ended during fault resolution");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
const auto writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
@@ -586,127 +511,14 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
const auto old_protection = NO_ACCESS_PROTECTION;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
m_impl->Protect(page, address, new_protection, old_protection, false);
|
||||
m_impl->Protect(page, address, new_protection, old_protection);
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
page.backing_writer = 0;
|
||||
page.resolving = false;
|
||||
page.resolving_read_write = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("nested HandleFault call");
|
||||
}
|
||||
auto* region = m_impl->FindRegion(fault_vaddr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, fault_vaddr);
|
||||
bool waited = false;
|
||||
while (true) {
|
||||
SpinGuard lock(page.lock);
|
||||
if (access == PageFaultAccess::Read && page.late_read_pending &&
|
||||
Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
page.late_read_pending = false;
|
||||
return true;
|
||||
}
|
||||
if (access == PageFaultAccess::Write && page.late_write_pending &&
|
||||
Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
page.late_write_pending = false;
|
||||
return true;
|
||||
}
|
||||
if (page.resolving) {
|
||||
if (page.backing_writer == CurrentThread()) {
|
||||
FailFast("backing writer faulted on its own reserved page");
|
||||
}
|
||||
if ((!page.resolving_read_write && access != PageFaultAccess::Write) ||
|
||||
(page.resolving_read_write && access != PageFaultAccess::Read &&
|
||||
access != PageFaultAccess::Write)) {
|
||||
FailFast("fault access is incompatible with the active resolver");
|
||||
}
|
||||
waited = true;
|
||||
continue;
|
||||
}
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
|
||||
return false;
|
||||
}
|
||||
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
|
||||
: page.late_write_pending);
|
||||
const bool allowed = Impl::AllowsAccess(page, fault_vaddr, access);
|
||||
pending = false;
|
||||
if (waited && !allowed) {
|
||||
FailFast("page remained inaccessible after waiting for its resolver");
|
||||
}
|
||||
// More than one CPU can fault before a protection transition becomes visible. The first
|
||||
// delayed fault consumes the hint bit; later faults must also resume once the mapped
|
||||
// page already permits the requested access. A genuinely read-only/no-access page still
|
||||
// falls through to the guest exception path.
|
||||
return allowed;
|
||||
}
|
||||
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
|
||||
(access == PageFaultAccess::Read && page.access_watchers == 0)) {
|
||||
FailFast("fault access is incompatible with active page watchers");
|
||||
}
|
||||
page.resolving = true;
|
||||
page.resolving_read_write = page.access_watchers != 0;
|
||||
break;
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool handled = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Invalidate);
|
||||
g_in_fault_resolution = false;
|
||||
{
|
||||
SpinGuard lock(page.lock);
|
||||
if (!handled || !page.resolving) {
|
||||
FailFast("fault invalidation did not preserve the resolving state");
|
||||
}
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool completed = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Complete);
|
||||
g_in_fault_resolution = false;
|
||||
{
|
||||
SpinGuard lock(page.lock);
|
||||
if (!completed || !page.resolving) {
|
||||
FailFast("fault completion did not preserve the resolving state");
|
||||
}
|
||||
if (page.write_watchers != 0 || page.access_watchers != 0) {
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
const bool read_only_fault = access == PageFaultAccess::Read;
|
||||
if (read_only_fault && page.access_watchers == 0) {
|
||||
FailFast("read fault completed without a read/write watcher");
|
||||
}
|
||||
page.access_watchers = 0;
|
||||
if (!read_only_fault) {
|
||||
page.write_watchers = 0;
|
||||
}
|
||||
const auto restored_protection = Impl::WatcherProtection(page);
|
||||
m_impl->Protect(page, PageStart(fault_vaddr), restored_protection, old_protection,
|
||||
true);
|
||||
if (page.write_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, restored_protection);
|
||||
} else if (!Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
FailFast("fault completion left the page inaccessible");
|
||||
}
|
||||
page.resolving = false;
|
||||
page.resolving_read_write = false;
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool released = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Release);
|
||||
g_in_fault_resolution = false;
|
||||
if (!released) {
|
||||
FailFast("fault release callback failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -11,11 +11,8 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class PageFaultAccess { Read, Write, Execute, Unknown };
|
||||
enum class PageFaultPhase { Invalidate, Complete, Release };
|
||||
enum class PageWatchMode { Write, ReadWrite };
|
||||
|
||||
using PageFaultHandler = bool (*)(void* context, PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
class PageManager final {
|
||||
public:
|
||||
class BackingWrite final {
|
||||
@@ -30,21 +27,19 @@ public:
|
||||
uint64_t m_size = 0;
|
||||
};
|
||||
|
||||
PageManager(PageFaultHandler fault_handler, void* fault_context);
|
||||
PageManager();
|
||||
// The owner must stop all PageManager callers before destruction.
|
||||
~PageManager();
|
||||
|
||||
KYTY_CLASS_NO_COPY(PageManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetPageSize() const;
|
||||
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
|
||||
|
||||
void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode = PageWatchMode::Write);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
|
||||
ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class CpuFaultAction { Untracked, Continue, Download };
|
||||
|
||||
class TrackingSpinLock final {
|
||||
public:
|
||||
void lock() noexcept {
|
||||
@@ -85,18 +83,6 @@ public:
|
||||
KYTY_CLASS_NO_COPY(RegionManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
|
||||
void Track(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.set(page);
|
||||
}
|
||||
}
|
||||
void Untrack(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.reset(page);
|
||||
}
|
||||
}
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
@@ -126,15 +112,15 @@ public:
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
if constexpr (source == DirtySource::Cpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n");
|
||||
if (m_gpu_dirty.test(page)) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n");
|
||||
if (m_cpu_dirty.test(page)) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,110 +137,10 @@ public:
|
||||
return changed;
|
||||
}
|
||||
|
||||
[[nodiscard]] CpuFaultAction BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) {
|
||||
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
|
||||
EXIT("unsupported CPU fault access while beginning ownership transfer\n");
|
||||
}
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
const bool tracked = m_tracked.test(start);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_tracked.test(page) != tracked) {
|
||||
EXIT("CPU fault spans mixed tracked and untracked pages\n");
|
||||
}
|
||||
if (m_fault_pending.test(page)) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
if (m_cpu_dirty.test(page) != m_writable.test(page) ||
|
||||
(m_gpu_dirty.test(page) && (m_cpu_dirty.test(page) || m_writable.test(page)))) {
|
||||
EXIT("inconsistent CPU fault page state\n");
|
||||
}
|
||||
}
|
||||
if (!tracked) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
bool gpu_dirty = m_gpu_dirty.test(start);
|
||||
bool writable = m_writable.test(start);
|
||||
for (auto page = start + 1; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) != gpu_dirty || m_writable.test(page) != writable) {
|
||||
EXIT("CPU fault spans pages with incompatible dirty or writable state\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!gpu_dirty && access == PageFaultAccess::Write) {
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
}
|
||||
m_fault_pending.set(page);
|
||||
}
|
||||
return gpu_dirty ? CpuFaultAction::Download : CpuFaultAction::Continue;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
const bool gpu_dirty = m_gpu_dirty.test(page);
|
||||
if (gpu_dirty != downloaded) {
|
||||
EXIT("CPU fault download result disagrees with GPU dirty state\n");
|
||||
}
|
||||
if (gpu_dirty) {
|
||||
m_gpu_dirty.reset(page);
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read: break;
|
||||
case PageFaultAccess::Write:
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
break;
|
||||
default: EXIT("unsupported CPU fault access after GPU download\n");
|
||||
}
|
||||
}
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasPendingFault(uint64_t vaddr, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_fault_pending.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteVirtualGpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_gpu_dirty.test(page)) {
|
||||
EXIT("virtual GPU write completion found a non-GPU-dirty page\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_gpu_dirty.reset(page);
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <DirtySource source, bool clear, typename Func>
|
||||
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
auto mask = GetBits<source>();
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
mask &= ~m_fault_pending;
|
||||
}
|
||||
for (auto page = 0u; page < start; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
@@ -351,8 +237,6 @@ private:
|
||||
RegionBits m_cpu_dirty;
|
||||
RegionBits m_gpu_dirty;
|
||||
RegionBits m_writable;
|
||||
RegionBits m_fault_pending;
|
||||
RegionBits m_tracked;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
+2
-110
@@ -123,24 +123,6 @@ struct BufferCache::RetiredBuffer {
|
||||
std::shared_ptr<Buffer> owner;
|
||||
};
|
||||
|
||||
struct BufferCache::FaultReadback {
|
||||
PageFaultAccess access = PageFaultAccess::Unknown;
|
||||
uint64_t vaddr = 0;
|
||||
uint64_t size = 0;
|
||||
std::vector<DownloadRange> ranges;
|
||||
bool installed = false;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return !ranges.empty(); }
|
||||
|
||||
void Reset() {
|
||||
access = PageFaultAccess::Unknown;
|
||||
vaddr = 0;
|
||||
size = 0;
|
||||
installed = false;
|
||||
ranges.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferCache::PendingBackingPublication {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
@@ -253,7 +235,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
ResourceMutex& resource_mutex)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_fault_readback(std::make_unique<FaultReadback>()), m_memory_tracker(page_manager),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
@@ -277,9 +259,6 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
}
|
||||
|
||||
BufferCache::~BufferCache() {
|
||||
if (m_fault_readback->Active()) {
|
||||
EXIT("BufferCache: destroyed with an active fault readback\n");
|
||||
}
|
||||
if (!m_gpu_modified_ranges.Empty()) {
|
||||
EXIT("BufferCache: destroyed with pending GPU-modified ranges\n");
|
||||
}
|
||||
@@ -447,93 +426,6 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
const auto page = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
if (size == 0 || size > page + TRACKER_PAGE_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid page-fault range\n");
|
||||
}
|
||||
|
||||
if (phase == PageFaultPhase::Complete) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& fault = *m_fault_readback;
|
||||
if (!fault.Active()) {
|
||||
return m_memory_tracker.CompleteCpuFault(vaddr, size, access, false);
|
||||
}
|
||||
if (fault.access != access || fault.vaddr != vaddr || fault.size != size ||
|
||||
fault.installed) {
|
||||
EXIT("BufferCache: mismatched fault readback completion\n");
|
||||
}
|
||||
PublishDownloads(fault.ranges);
|
||||
if (!m_memory_tracker.CompleteCpuFault(vaddr, size, access, true)) {
|
||||
EXIT("BufferCache: failed to complete downloaded CPU fault\n");
|
||||
}
|
||||
fault.installed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (phase == PageFaultPhase::Release) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& fault = *m_fault_readback;
|
||||
if (fault.Active()) {
|
||||
if (fault.access != access || fault.vaddr != vaddr || fault.size != size ||
|
||||
!fault.installed) {
|
||||
EXIT("BufferCache: mismatched fault readback release\n");
|
||||
}
|
||||
for (const auto& range: fault.ranges) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
fault.Reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (phase != PageFaultPhase::Invalidate) {
|
||||
EXIT("BufferCache: unsupported page-fault phase\n");
|
||||
}
|
||||
|
||||
const auto action = m_memory_tracker.BeginCpuFault(vaddr, size, access);
|
||||
if (action != CpuFaultAction::Download) {
|
||||
return action == CpuFaultAction::Continue;
|
||||
}
|
||||
|
||||
auto& fault = *m_fault_readback;
|
||||
std::vector<DownloadCopy> copies;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
if (fault.Active()) {
|
||||
EXIT("BufferCache: nested fault readback\n");
|
||||
}
|
||||
fault.access = access;
|
||||
fault.vaddr = vaddr;
|
||||
fault.size = size;
|
||||
|
||||
m_gpu_modified_ranges.ForEachIntersection(
|
||||
page, TRACKER_PAGE_SIZE, [&](RangeSet::Range range) {
|
||||
auto owner = m_buffers.upper_bound(range.address);
|
||||
if (owner == m_buffers.begin()) {
|
||||
EXIT("BufferCache: fault readback has no buffer owner\n");
|
||||
}
|
||||
--owner;
|
||||
auto& cached = *owner->second;
|
||||
if (!cached.buffer->IsInBounds(range.address, range.size)) {
|
||||
EXIT("BufferCache: fault readback is outside its buffer owner\n");
|
||||
}
|
||||
copies.push_back({cached.buffer, cached.buffer->Offset(range.address),
|
||||
range.address, range.size});
|
||||
});
|
||||
if (copies.empty()) {
|
||||
EXIT("BufferCache: GPU-dirty fault page has no dirty byte ranges\n");
|
||||
}
|
||||
}
|
||||
fault.ranges = RecordDownloads(copies);
|
||||
if (!fault.Active()) {
|
||||
EXIT("BufferCache: GPU-dirty fault page has no dirty byte ranges\n");
|
||||
}
|
||||
m_scheduler.FinishCurrent();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid unmap range\n");
|
||||
@@ -1201,7 +1093,7 @@ void BufferCache::RunGarbageCollector() {
|
||||
if (m_graphics.CanReportMemoryUsage()) {
|
||||
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
|
||||
}
|
||||
if (m_total_used_memory < m_trigger_gc_memory || m_fault_readback->Active()) {
|
||||
if (m_total_used_memory < m_trigger_gc_memory) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ public:
|
||||
~BufferCache();
|
||||
KYTY_CLASS_NO_COPY(BufferCache);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
void ReadMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
@@ -90,7 +88,6 @@ private:
|
||||
struct DownloadCopy;
|
||||
struct DownloadRange;
|
||||
struct RetiredBuffer;
|
||||
struct FaultReadback;
|
||||
struct PendingBackingPublication;
|
||||
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
|
||||
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
|
||||
@@ -120,7 +117,6 @@ private:
|
||||
Common::Mutex m_mutex;
|
||||
std::shared_ptr<Buffer> m_null_buffer;
|
||||
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
|
||||
std::unique_ptr<FaultReadback> m_fault_readback;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
RangeSet m_image_invalidated_ranges;
|
||||
std::mutex m_publication_mutex;
|
||||
|
||||
+1
-23
@@ -7,33 +7,11 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_page_manager(FaultThunk, this),
|
||||
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
|
||||
: m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
|
||||
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
|
||||
|
||||
GpuResourceManager::~GpuResourceManager() = default;
|
||||
|
||||
bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept {
|
||||
return static_cast<GpuResourceManager*>(context)->InvalidateMemory(access, vaddr, size, phase);
|
||||
}
|
||||
|
||||
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
|
||||
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
|
||||
// make the image appear to race a real CPU write. Completion and release retain buffer-first
|
||||
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
|
||||
if (phase == PageFaultPhase::Invalidate) {
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
}
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
}
|
||||
|
||||
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
|
||||
constexpr uint64_t fault_size = 8;
|
||||
if (!IsMapped(fault_vaddr, fault_size)) {
|
||||
|
||||
@@ -34,11 +34,6 @@ public:
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
|
||||
PageManager m_page_manager;
|
||||
ResourceMutex m_resource_mutex;
|
||||
BufferCache m_buffer_cache;
|
||||
|
||||
@@ -1826,37 +1826,6 @@ bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
|
||||
!GuestRange {address, size}.Valid()) {
|
||||
return false;
|
||||
}
|
||||
if (access == PageFaultAccess::Read) {
|
||||
return false;
|
||||
}
|
||||
if (phase == PageFaultPhase::Invalidate) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const bool tracked =
|
||||
std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
|
||||
const auto owner = ResolveOwner(id);
|
||||
return owner != nullptr && !owner->depth_id && owner->IsTracked();
|
||||
});
|
||||
if (tracked) {
|
||||
InvalidateCpuAliases(address, size);
|
||||
}
|
||||
return tracked;
|
||||
}
|
||||
if (phase != PageFaultPhase::Complete && phase != PageFaultPhase::Release) {
|
||||
return false;
|
||||
}
|
||||
CacheLock lock(*this, m_lock);
|
||||
return std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
|
||||
const auto owner = ResolveOwner(id);
|
||||
return owner != nullptr && !owner->depth_id;
|
||||
});
|
||||
}
|
||||
|
||||
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
|
||||
@@ -76,8 +76,6 @@ public:
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
void ProcessDownloadImages();
|
||||
void RunGarbageCollector();
|
||||
|
||||
+16
-20
@@ -18,6 +18,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
@@ -805,11 +806,11 @@ private:
|
||||
Common::Mutex m_mutex;
|
||||
};
|
||||
|
||||
static PhysicalMemory* g_physical_memory = nullptr;
|
||||
static FlexibleMemory* g_flexible_memory = nullptr;
|
||||
static PooledMemory* g_pooled_memory = nullptr;
|
||||
static VirtualRanges* g_virtual_ranges = nullptr;
|
||||
static GuestAddressSpace* g_guest_address_space = nullptr;
|
||||
static std::unique_ptr<PhysicalMemory> g_physical_memory;
|
||||
static std::unique_ptr<FlexibleMemory> g_flexible_memory;
|
||||
static std::unique_ptr<PooledMemory> g_pooled_memory;
|
||||
static std::unique_ptr<VirtualRanges> g_virtual_ranges;
|
||||
static std::unique_ptr<GuestAddressSpace> g_guest_address_space;
|
||||
static callback_func_t g_alloc_callback = nullptr;
|
||||
static callback_func_t g_free_callback = nullptr;
|
||||
static std::atomic<uint64_t> g_memory_pool_committed = 0;
|
||||
@@ -970,11 +971,11 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size);
|
||||
KYTY_SUBSYSTEM_INIT(Memory) {
|
||||
g_flexible_memory_size_frozen = true;
|
||||
VirtualMemory::Init();
|
||||
g_guest_address_space = new GuestAddressSpace(PhysicalMemory::TotalSize());
|
||||
g_physical_memory = new PhysicalMemory;
|
||||
g_flexible_memory = new FlexibleMemory;
|
||||
g_pooled_memory = new PooledMemory;
|
||||
g_virtual_ranges = new VirtualRanges;
|
||||
g_guest_address_space = std::make_unique<GuestAddressSpace>(PhysicalMemory::TotalSize());
|
||||
g_physical_memory = std::make_unique<PhysicalMemory>();
|
||||
g_flexible_memory = std::make_unique<FlexibleMemory>();
|
||||
g_pooled_memory = std::make_unique<PooledMemory>();
|
||||
g_virtual_ranges = std::make_unique<VirtualRanges>();
|
||||
EXIT_IF(!g_guest_address_space->SelfTest());
|
||||
EXIT_IF(!SelfTestSub64SharedPlaceholderAlias());
|
||||
}
|
||||
@@ -982,16 +983,11 @@ KYTY_SUBSYSTEM_INIT(Memory) {
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Memory) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Memory) {
|
||||
delete g_pooled_memory;
|
||||
g_pooled_memory = nullptr;
|
||||
delete g_flexible_memory;
|
||||
g_flexible_memory = nullptr;
|
||||
delete g_physical_memory;
|
||||
g_physical_memory = nullptr;
|
||||
delete g_virtual_ranges;
|
||||
g_virtual_ranges = nullptr;
|
||||
delete g_guest_address_space;
|
||||
g_guest_address_space = nullptr;
|
||||
g_pooled_memory.reset();
|
||||
g_flexible_memory.reset();
|
||||
g_physical_memory.reset();
|
||||
g_virtual_ranges.reset();
|
||||
g_guest_address_space.reset();
|
||||
}
|
||||
|
||||
struct AlignedPos {
|
||||
|
||||
+175
-928
File diff suppressed because it is too large
Load Diff
+127
-517
@@ -1,14 +1,12 @@
|
||||
#include "common/virtualMemory.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -19,20 +17,18 @@
|
||||
#undef min
|
||||
#undef max
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using Libs::Graphics::PageFaultAccess;
|
||||
using Libs::Graphics::PageManager;
|
||||
using Libs::Graphics::PageWatchMode;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
void Check(bool value, const char *text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "PageManagerTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
@@ -40,7 +36,6 @@ void Check(bool value, const char* text) {
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// POSIX shims for the shared test body.
|
||||
using DWORD = uint32_t;
|
||||
constexpr uint32_t PAGE_NOACCESS = 1;
|
||||
constexpr uint32_t PAGE_READONLY = 2;
|
||||
@@ -49,22 +44,25 @@ constexpr uint32_t MEM_RELEASE = 0;
|
||||
|
||||
int ToHostProt(uint32_t protection) {
|
||||
switch (protection) {
|
||||
case PAGE_NOACCESS: return PROT_NONE;
|
||||
case PAGE_READONLY: return PROT_READ;
|
||||
default: return PROT_READ | PROT_WRITE;
|
||||
case PAGE_NOACCESS:
|
||||
return PROT_NONE;
|
||||
case PAGE_READONLY:
|
||||
return PROT_READ;
|
||||
default:
|
||||
return PROT_READ | PROT_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Protection(const void* address) {
|
||||
uint32_t Protection(const void *address) {
|
||||
const auto addr = reinterpret_cast<uintptr_t>(address);
|
||||
std::FILE* maps = std::fopen("/proc/self/maps", "r");
|
||||
std::FILE *maps = std::fopen("/proc/self/maps", "r");
|
||||
Check(maps != nullptr, "open /proc/self/maps failed");
|
||||
char line[512];
|
||||
uint32_t result = 0; // 0 => not mapped at all
|
||||
uint32_t result = 0;
|
||||
while (std::fgets(line, sizeof(line), maps) != nullptr) {
|
||||
unsigned long start = 0;
|
||||
unsigned long end = 0;
|
||||
char perms[8] {};
|
||||
char perms[8]{};
|
||||
if (std::sscanf(line, "%lx-%lx %7s", &start, &end, perms) != 3) {
|
||||
continue;
|
||||
}
|
||||
@@ -79,18 +77,13 @@ uint32_t Protection(const void* address) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsWritable(const void* address) {
|
||||
return Protection(address) == PAGE_READWRITE;
|
||||
}
|
||||
|
||||
// munmap needs the length that VirtualFree's callers pass as 0, so sizes are remembered here.
|
||||
std::map<void*, size_t>& AllocationSizes() {
|
||||
static std::map<void*, size_t> sizes;
|
||||
std::map<void *, size_t> &AllocationSizes() {
|
||||
static std::map<void *, size_t> sizes;
|
||||
return sizes;
|
||||
}
|
||||
|
||||
int VirtualFree(void* address, size_t /*size*/, DWORD /*type*/) {
|
||||
auto& sizes = AllocationSizes();
|
||||
int VirtualFree(void *address, size_t, DWORD) {
|
||||
auto &sizes = AllocationSizes();
|
||||
auto it = sizes.find(address);
|
||||
if (it == sizes.end()) {
|
||||
return 0;
|
||||
@@ -100,31 +93,29 @@ int VirtualFree(void* address, size_t /*size*/, DWORD /*type*/) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
int VirtualProtect(void* address, size_t size, uint32_t protection, DWORD* old_protection) {
|
||||
int VirtualProtect(void *address, size_t size, uint32_t protection,
|
||||
DWORD *old_protection) {
|
||||
if (old_protection != nullptr) {
|
||||
*old_protection = Protection(address);
|
||||
}
|
||||
return ::mprotect(address, size, ToHostProt(protection)) == 0 ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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 {};
|
||||
#else
|
||||
uint32_t Protection(const void *address) {
|
||||
MEMORY_BASIC_INFORMATION info{};
|
||||
Check(VirtualQuery(address, &info, sizeof(info)) != 0, "VirtualQuery failed");
|
||||
return info.Protect;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::atomic_uint64_t g_protection_calls {0};
|
||||
bool IsWritable(const void *address) {
|
||||
return Protection(address) == PAGE_READWRITE;
|
||||
}
|
||||
|
||||
bool ProtectAddressSpace(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) {
|
||||
uint64_t g_protection_calls = 0;
|
||||
|
||||
bool ProtectAddressSpace(uint64_t vaddr, uint64_t size,
|
||||
Common::VirtualMemory::Mode mode) {
|
||||
uint32_t protection = PAGE_NOACCESS;
|
||||
if (mode == Common::VirtualMemory::Mode::Read) {
|
||||
protection = PAGE_READONLY;
|
||||
@@ -132,397 +123,125 @@ bool ProtectAddressSpace(uint64_t vaddr, uint64_t size, Common::VirtualMemory::M
|
||||
protection = PAGE_READWRITE;
|
||||
}
|
||||
DWORD old_protection = 0;
|
||||
g_protection_calls.fetch_add(1, std::memory_order_relaxed);
|
||||
return VirtualProtect(reinterpret_cast<void*>(vaddr), size, protection, &old_protection) != 0;
|
||||
g_protection_calls++;
|
||||
return VirtualProtect(reinterpret_cast<void *>(vaddr), size, protection,
|
||||
&old_protection) != 0;
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
struct FaultContext {
|
||||
PageManager* manager = nullptr;
|
||||
bool result = true;
|
||||
bool reenter = false;
|
||||
uint64_t reenter_address = 0;
|
||||
bool block = false;
|
||||
std::atomic_uint32_t calls {0};
|
||||
std::atomic_bool entered {false};
|
||||
std::atomic_bool release {false};
|
||||
};
|
||||
|
||||
std::atomic<PageManager*> g_native_fault_manager {nullptr};
|
||||
std::atomic_bool g_delay_native_fault {false};
|
||||
std::atomic_bool g_native_fault_entered {false};
|
||||
std::atomic_bool g_release_native_fault {false};
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
LONG CALLBACK NativeFaultHandler(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* manager = g_native_fault_manager.load(std::memory_order_acquire);
|
||||
if (manager == nullptr) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
if (g_delay_native_fault.load(std::memory_order_acquire)) {
|
||||
g_native_fault_entered.store(true, std::memory_order_release);
|
||||
while (!g_release_native_fault.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
return manager->HandleFault(access, exception->ExceptionRecord->ExceptionInformation[1])
|
||||
? EXCEPTION_CONTINUE_EXECUTION
|
||||
: EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
#else
|
||||
// SIGSEGV stands in for the vectored exception handler.
|
||||
void NativeFaultHandler(int signal_number, siginfo_t* info, void* native_context) {
|
||||
auto* context = static_cast<ucontext_t*>(native_context);
|
||||
const auto error_code = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_ERR]);
|
||||
const auto access = (error_code & 0x10u) != 0 ? PageFaultAccess::Execute
|
||||
: (error_code & 0x02u) != 0 ? PageFaultAccess::Write
|
||||
: PageFaultAccess::Read;
|
||||
auto* manager = g_native_fault_manager.load(std::memory_order_acquire);
|
||||
if (manager != nullptr) {
|
||||
if (g_delay_native_fault.load(std::memory_order_acquire)) {
|
||||
g_native_fault_entered.store(true, std::memory_order_release);
|
||||
while (!g_release_native_fault.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
if (manager->HandleFault(access, reinterpret_cast<uint64_t>(info->si_addr))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
struct sigaction restore {};
|
||||
restore.sa_handler = SIG_DFL;
|
||||
sigemptyset(&restore.sa_mask);
|
||||
::sigaction(signal_number, &restore, nullptr);
|
||||
}
|
||||
|
||||
struct sigaction g_saved_segv_action {};
|
||||
|
||||
void* AddVectoredExceptionHandler(unsigned long /*first*/,
|
||||
void (*handler)(int, siginfo_t*, void*)) {
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = handler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
if (::sigaction(SIGSEGV, &action, &g_saved_segv_action) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<void*>(handler);
|
||||
}
|
||||
|
||||
int RemoveVectoredExceptionHandler(void* /*token*/) {
|
||||
return ::sigaction(SIGSEGV, &g_saved_segv_action, nullptr) == 0 ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool InvalidateFault(void* context, Libs::Graphics::PageFaultAccess, uint64_t vaddr, uint64_t size,
|
||||
Libs::Graphics::PageFaultPhase phase) noexcept {
|
||||
auto* fault = static_cast<FaultContext*>(context);
|
||||
Check(fault != nullptr && fault->manager != nullptr, "invalid fault context");
|
||||
if (phase != Libs::Graphics::PageFaultPhase::Invalidate) {
|
||||
return true;
|
||||
}
|
||||
fault->calls.fetch_add(1, std::memory_order_relaxed);
|
||||
if (fault->reenter) {
|
||||
const auto address = fault->reenter_address != 0 ? fault->reenter_address : vaddr;
|
||||
(void)fault->manager->HandleFault(PageFaultAccess::Write, address);
|
||||
}
|
||||
if (fault->block) {
|
||||
fault->entered.store(true, std::memory_order_release);
|
||||
while (!fault->release.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
(void)size;
|
||||
return fault->result;
|
||||
}
|
||||
|
||||
uint8_t* Allocate(uint64_t size, uint32_t protection = PAGE_READWRITE) {
|
||||
uint8_t *Allocate(uint64_t size, uint32_t protection = PAGE_READWRITE) {
|
||||
constexpr uintptr_t test_address = 0x0000000200010000ull;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
auto* memory = static_cast<uint8_t*>(VirtualAlloc(reinterpret_cast<void*>(test_address), size,
|
||||
auto *memory = static_cast<uint8_t *>(
|
||||
VirtualAlloc(reinterpret_cast<void *>(test_address), size,
|
||||
MEM_RESERVE | MEM_COMMIT, protection));
|
||||
Check(memory == reinterpret_cast<void*>(test_address), "fixed low VirtualAlloc failed");
|
||||
Check(memory == reinterpret_cast<void *>(test_address),
|
||||
"fixed low VirtualAlloc failed");
|
||||
#else
|
||||
// Do not overwrite a leaked mapping from an earlier case.
|
||||
void* raw = ::mmap(reinterpret_cast<void*>(test_address), size, ToHostProt(protection),
|
||||
void *raw = ::mmap(reinterpret_cast<void *>(test_address), size,
|
||||
ToHostProt(protection),
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
||||
Check(raw == reinterpret_cast<void*>(test_address), "fixed low mmap failed");
|
||||
auto* memory = static_cast<uint8_t*>(raw);
|
||||
Check(raw == reinterpret_cast<void *>(test_address), "fixed low mmap failed");
|
||||
auto *memory = static_cast<uint8_t *>(raw);
|
||||
AllocationSizes()[raw] = static_cast<size_t>(size);
|
||||
#endif
|
||||
return memory;
|
||||
}
|
||||
|
||||
void TestWatchFaultAndUnwatch() {
|
||||
g_protection_calls.store(0, std::memory_order_relaxed);
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
void TestWatchAndUnwatch() {
|
||||
g_protection_calls = 0;
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size * 2);
|
||||
auto *memory = Allocate(page_size * 2);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(reinterpret_cast<uint64_t>(memory), page_size * 2);
|
||||
manager.UpdatePageWatchers(true, reinterpret_cast<uint64_t>(memory), page_size);
|
||||
Check(manager.IsTracked(reinterpret_cast<uint64_t>(memory)) && !IsWritable(memory),
|
||||
"watch did not protect the page");
|
||||
Check(g_protection_calls.load(std::memory_order_relaxed) != 0,
|
||||
manager.OnGpuMap(address, page_size * 2);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
Check(Protection(memory) == PAGE_READONLY && IsWritable(memory + page_size),
|
||||
"write watch installed incorrect protections");
|
||||
Check(g_protection_calls != 0,
|
||||
"watch protection bypassed the address-space owner callback");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, reinterpret_cast<uint64_t>(memory + 32)),
|
||||
"tracked write fault was not handled");
|
||||
Check(!manager.IsTracked(reinterpret_cast<uint64_t>(memory)) && IsWritable(memory),
|
||||
"fault invalidation did not remove the watcher");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, reinterpret_cast<uint64_t>(memory)),
|
||||
"single delayed write fault was not coalesced");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, reinterpret_cast<uint64_t>(memory)),
|
||||
"second delayed write fault was not coalesced");
|
||||
manager.OnGpuUnmap(reinterpret_cast<uint64_t>(memory), page_size * 2);
|
||||
manager.UpdatePageWatchers(false, address, page_size);
|
||||
Check(IsWritable(memory), "write unwatch did not restore access");
|
||||
manager.OnGpuUnmap(address, page_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestSharedWatcherFault() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
void TestSharedWatcherCounts() {
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
auto *memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address + 8, 32);
|
||||
manager.UpdatePageWatchers(true, address + 128, 64);
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address + 16),
|
||||
"shared-watcher fault was not handled");
|
||||
Check(!manager.IsTracked(address) && IsWritable(memory),
|
||||
"fault callback did not clear every shared watcher");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestReadWriteWatcherFault() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size, Libs::Graphics::PageWatchMode::Write);
|
||||
manager.UpdatePageWatchers(true, address, page_size, Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
manager.UpdatePageWatchers(false, address, page_size, Libs::Graphics::PageWatchMode::Write);
|
||||
Check(Protection(memory) == PAGE_NOACCESS,
|
||||
"read/write watcher did not install no-access protection");
|
||||
Check(manager.HandleFault(PageFaultAccess::Read, address + 8),
|
||||
"tracked read fault was not handled");
|
||||
Check(Protection(memory) == PAGE_READWRITE && !manager.IsTracked(address),
|
||||
"read fault did not release every watcher");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestPermittedMappedLateFaultsResume() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
manager.UpdatePageWatchers(false, address, page_size);
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"first delayed mapped write was not accepted");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"second delayed mapped write was not accepted");
|
||||
Check(manager.HandleFault(PageFaultAccess::Read, address),
|
||||
"delayed mapped read was not accepted on readable backing");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestPartialMappingUnmapPreservesTokens() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.OnGpuMap(address + 8, 16);
|
||||
manager.UpdatePageWatchers(true, address, page_size, Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
manager.UpdatePageWatchers(false, address, page_size, Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
manager.OnGpuUnmap(address + 8, 16);
|
||||
Check(manager.HandleFault(PageFaultAccess::Read, address),
|
||||
"partial mapping unmap erased delayed read ownership");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"partial mapping unmap erased delayed write ownership");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestNativeDelayedReadAfterModeDowngrade() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
memory[0] = 0x6d;
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size, Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
manager.UpdatePageWatchers(false, address, page_size);
|
||||
Check(Protection(memory) == PAGE_NOACCESS,
|
||||
"read/write ownership did not install no-access protection");
|
||||
|
||||
void* handler = AddVectoredExceptionHandler(1, NativeFaultHandler);
|
||||
Check(handler != nullptr, "AddVectoredExceptionHandler failed");
|
||||
Check(g_native_fault_manager.exchange(&manager, std::memory_order_acq_rel) == nullptr,
|
||||
"native fault manager already installed");
|
||||
g_native_fault_entered.store(false, std::memory_order_release);
|
||||
g_release_native_fault.store(false, std::memory_order_release);
|
||||
g_delay_native_fault.store(true, std::memory_order_release);
|
||||
|
||||
uint8_t value = 0;
|
||||
std::thread reader([&] { value = *static_cast<volatile uint8_t*>(memory); });
|
||||
while (!g_native_fault_entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
manager.UpdatePageWatchers(false, address, page_size, Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
manager.UpdatePageWatchers(false, address + 8, 32);
|
||||
Check(Protection(memory) == PAGE_READONLY,
|
||||
"mode downgrade did not restore readable protection");
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
|
||||
context.block = true;
|
||||
bool write_handled = false;
|
||||
std::thread writer(
|
||||
[&] { write_handled = manager.HandleFault(PageFaultAccess::Write, address); });
|
||||
while (!context.entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
g_release_native_fault.store(true, std::memory_order_release);
|
||||
reader.join();
|
||||
context.release.store(true, std::memory_order_release);
|
||||
writer.join();
|
||||
|
||||
g_delay_native_fault.store(false, std::memory_order_release);
|
||||
Check(g_native_fault_manager.exchange(nullptr, std::memory_order_acq_rel) == &manager,
|
||||
"native fault manager publication changed");
|
||||
Check(RemoveVectoredExceptionHandler(handler) != 0, "RemoveVectoredExceptionHandler failed");
|
||||
Check(value == 0x6d && write_handled && context.calls.load(std::memory_order_relaxed) == 1,
|
||||
"delayed read was not coalesced across write ownership/resolution");
|
||||
|
||||
"first unwatch released a shared watcher");
|
||||
manager.UpdatePageWatchers(false, address + 128, 64);
|
||||
Check(IsWritable(memory), "last unwatch did not restore access");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestDelayedFaultAfterExplicitUnwatch() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
void TestMixedWatcherModes() {
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
auto *memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
|
||||
// Models a store that already raised an AV before another thread published
|
||||
// explicit CPU dirtiness and removed the watcher.
|
||||
manager.UpdatePageWatchers(false, address, page_size);
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"delayed watched write was not accepted after explicit unwatch");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address) &&
|
||||
context.calls.load(std::memory_order_relaxed) == 0,
|
||||
"second explicit-unwatch fault was not coalesced or dispatched invalidation");
|
||||
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestNativeAccessViolation() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
|
||||
void* handler = AddVectoredExceptionHandler(1, NativeFaultHandler);
|
||||
Check(handler != nullptr, "AddVectoredExceptionHandler failed");
|
||||
Check(g_native_fault_manager.exchange(&manager, std::memory_order_acq_rel) == nullptr,
|
||||
"native fault manager already installed");
|
||||
*static_cast<volatile uint8_t*>(memory) = 0x5a;
|
||||
Check(g_native_fault_manager.exchange(nullptr, std::memory_order_acq_rel) == &manager,
|
||||
"native fault manager publication changed");
|
||||
Check(RemoveVectoredExceptionHandler(handler) != 0, "RemoveVectoredExceptionHandler failed");
|
||||
|
||||
Check(memory[0] == 0x5a && !manager.IsTracked(address) &&
|
||||
context.calls.load(std::memory_order_relaxed) == 1,
|
||||
"native access violation did not invalidate and resume the store");
|
||||
manager.UpdatePageWatchers(true, address, page_size, PageWatchMode::Write);
|
||||
manager.UpdatePageWatchers(true, address, page_size,
|
||||
PageWatchMode::ReadWrite);
|
||||
Check(Protection(memory) == PAGE_NOACCESS,
|
||||
"read/write watcher did not deny access");
|
||||
manager.UpdatePageWatchers(false, address, page_size, PageWatchMode::Write);
|
||||
Check(Protection(memory) == PAGE_NOACCESS,
|
||||
"write unwatch released a read/write watcher");
|
||||
manager.UpdatePageWatchers(false, address, page_size,
|
||||
PageWatchMode::ReadWrite);
|
||||
Check(IsWritable(memory), "read/write unwatch did not restore access");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestCrossRegionRange() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
constexpr uint64_t region_size = 4ull * 1024ull * 1024ull;
|
||||
auto* memory = Allocate(region_size * 2);
|
||||
auto *memory = Allocate(region_size * 2);
|
||||
const auto base = reinterpret_cast<uint64_t>(memory);
|
||||
const auto boundary = (base + region_size - 1) & ~(region_size - 1);
|
||||
Check(boundary >= base + page_size && boundary + page_size <= base + region_size * 2,
|
||||
Check(boundary >= base + page_size &&
|
||||
boundary + page_size <= base + region_size * 2,
|
||||
"test allocation does not contain a region boundary");
|
||||
|
||||
manager.OnGpuMap(base, region_size * 2);
|
||||
manager.UpdatePageWatchers(true, boundary - page_size, page_size * 2);
|
||||
Check(!IsWritable(reinterpret_cast<void*>(boundary - page_size)) &&
|
||||
!IsWritable(reinterpret_cast<void*>(boundary)),
|
||||
Check(!IsWritable(reinterpret_cast<void *>(boundary - page_size)) &&
|
||||
!IsWritable(reinterpret_cast<void *>(boundary)),
|
||||
"cross-region watch did not protect both pages");
|
||||
manager.UpdatePageWatchers(false, boundary - page_size, page_size * 2);
|
||||
Check(IsWritable(reinterpret_cast<void*>(boundary - page_size)) &&
|
||||
IsWritable(reinterpret_cast<void*>(boundary)),
|
||||
Check(IsWritable(reinterpret_cast<void *>(boundary - page_size)) &&
|
||||
IsWritable(reinterpret_cast<void *>(boundary)),
|
||||
"cross-region unwatch did not restore both pages");
|
||||
manager.OnGpuUnmap(base, region_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestBatchedWatcherRanges() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
constexpr uint64_t region_size = 4ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t allocation_size = region_size * 3;
|
||||
auto* memory = Allocate(allocation_size);
|
||||
auto *memory = Allocate(allocation_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
|
||||
manager.OnGpuMap(address, allocation_size);
|
||||
|
||||
manager.UpdatePageWatchers(true, address + page_size, page_size);
|
||||
manager.UpdatePageWatchers(true, address + page_size * 3, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size * 5);
|
||||
for (uint64_t page = 0; page < 5; page++) {
|
||||
Check(Protection(memory + page * page_size) == PAGE_READONLY,
|
||||
"fragmented watch did not coalesce to read-only");
|
||||
}
|
||||
manager.UpdatePageWatchers(false, address, page_size * 5);
|
||||
Check(IsWritable(memory) && Protection(memory + page_size) == PAGE_READONLY &&
|
||||
IsWritable(memory + page_size * 2) &&
|
||||
@@ -532,103 +251,60 @@ void TestBatchedWatcherRanges() {
|
||||
manager.UpdatePageWatchers(false, address + page_size, page_size);
|
||||
manager.UpdatePageWatchers(false, address + page_size * 3, page_size);
|
||||
|
||||
g_protection_calls = 0;
|
||||
manager.UpdatePageWatchers(true, address, allocation_size);
|
||||
Check(!IsWritable(memory) && !IsWritable(memory + region_size) &&
|
||||
!IsWritable(memory + region_size * 2) &&
|
||||
!IsWritable(memory + allocation_size - page_size),
|
||||
"large cross-region watch did not protect the full range");
|
||||
Check(g_protection_calls == 4 && Protection(memory) == PAGE_READONLY &&
|
||||
Protection(memory + region_size) == PAGE_READONLY &&
|
||||
Protection(memory + region_size * 2) == PAGE_READONLY &&
|
||||
Protection(memory + allocation_size - page_size) == PAGE_READONLY,
|
||||
"large watch was not batched and protected by tracking region");
|
||||
manager.UpdatePageWatchers(false, address, allocation_size);
|
||||
Check(IsWritable(memory) && IsWritable(memory + region_size) &&
|
||||
IsWritable(memory + region_size * 2) &&
|
||||
IsWritable(memory + allocation_size - page_size),
|
||||
"large cross-region unwatch did not restore the full range");
|
||||
|
||||
manager.UpdatePageWatchers(true, address, page_size * 5);
|
||||
manager.UpdatePageWatchers(true, address + page_size, page_size * 3,
|
||||
Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
Check(Protection(memory) == PAGE_READONLY && Protection(memory + page_size) == PAGE_NOACCESS &&
|
||||
Protection(memory + page_size * 2) == PAGE_NOACCESS &&
|
||||
Protection(memory + page_size * 3) == PAGE_NOACCESS &&
|
||||
Protection(memory + page_size * 4) == PAGE_READONLY,
|
||||
"mixed watcher modes installed incorrect protections");
|
||||
manager.UpdatePageWatchers(false, address, page_size * 5);
|
||||
Check(IsWritable(memory) && Protection(memory + page_size) == PAGE_NOACCESS &&
|
||||
Protection(memory + page_size * 2) == PAGE_NOACCESS &&
|
||||
Protection(memory + page_size * 3) == PAGE_NOACCESS &&
|
||||
IsWritable(memory + page_size * 4),
|
||||
"write unwatch incorrectly released read/write watchers");
|
||||
manager.UpdatePageWatchers(false, address + page_size, page_size * 3,
|
||||
Libs::Graphics::PageWatchMode::ReadWrite);
|
||||
Check(IsWritable(memory + page_size) && IsWritable(memory + page_size * 2) &&
|
||||
IsWritable(memory + page_size * 3),
|
||||
"read/write unwatch did not restore writable protection");
|
||||
|
||||
manager.OnGpuUnmap(address, allocation_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
[[noreturn]] void RunDeathCase(const char* name) {
|
||||
FaultContext context;
|
||||
auto manager = std::make_unique<PageManager>(InvalidateFault, &context);
|
||||
context.manager = manager.get();
|
||||
const auto page_size = manager->GetPageSize();
|
||||
[[noreturn]] void RunDeathCase(const char *name) {
|
||||
PageManager manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
if (std::strcmp(name, "invalid-range") == 0) {
|
||||
manager->UpdatePageWatchers(true, (1ull << 40u) - 1, 2);
|
||||
manager.UpdatePageWatchers(true, (1ull << 40u) - 1, 2);
|
||||
} else if (std::strcmp(name, "unknown-untrack") == 0) {
|
||||
manager->UpdatePageWatchers(false, 0x1000, page_size);
|
||||
} else {
|
||||
const bool two_pages = std::strcmp(name, "cross-reentrant") == 0;
|
||||
auto* memory = Allocate(two_pages ? page_size * 2 : page_size);
|
||||
manager.UpdatePageWatchers(false, 0x1000, page_size);
|
||||
} else if (std::strcmp(name, "destructor-watch") == 0) {
|
||||
auto doomed = std::make_unique<PageManager>();
|
||||
auto *memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager->OnGpuMap(address, two_pages ? page_size * 2 : page_size);
|
||||
manager->UpdatePageWatchers(true, address, page_size);
|
||||
if (two_pages) {
|
||||
manager->UpdatePageWatchers(true, address + page_size, page_size);
|
||||
}
|
||||
if (std::strcmp(name, "destructor-watch") == 0) {
|
||||
manager.reset();
|
||||
} else if (std::strcmp(name, "non-write") == 0) {
|
||||
(void)manager->HandleFault(PageFaultAccess::Read, address);
|
||||
} else if (std::strcmp(name, "callback-false") == 0) {
|
||||
context.result = false;
|
||||
(void)manager->HandleFault(PageFaultAccess::Write, address);
|
||||
} else if (std::strcmp(name, "reentrant") == 0) {
|
||||
context.reenter = true;
|
||||
(void)manager->HandleFault(PageFaultAccess::Write, address);
|
||||
} else if (std::strcmp(name, "cross-reentrant") == 0) {
|
||||
context.reenter = true;
|
||||
context.reenter_address = address + page_size;
|
||||
(void)manager->HandleFault(PageFaultAccess::Write, address);
|
||||
} else if (std::strcmp(name, "concurrent-non-write") == 0) {
|
||||
context.block = true;
|
||||
std::thread first([&] { (void)manager->HandleFault(PageFaultAccess::Write, address); });
|
||||
while (!context.entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
(void)manager->HandleFault(PageFaultAccess::Read, address);
|
||||
first.join();
|
||||
} else {
|
||||
std::_Exit(0x7f);
|
||||
}
|
||||
doomed->OnGpuMap(address, page_size);
|
||||
doomed->UpdatePageWatchers(true, address, page_size);
|
||||
doomed.reset();
|
||||
}
|
||||
std::_Exit(0x7f);
|
||||
}
|
||||
|
||||
void CheckDeathCase(const char* name) {
|
||||
void CheckDeathCase(const char *name) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
char path[MAX_PATH] {};
|
||||
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
|
||||
char path[MAX_PATH]{};
|
||||
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
|
||||
"GetModuleFileName failed");
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::vector<char> 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,
|
||||
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, "death test timed out");
|
||||
Check(WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0,
|
||||
"death test timed out");
|
||||
DWORD exit_code = 0;
|
||||
Check(GetExitCodeProcess(process.hProcess, &exit_code) != 0 &&
|
||||
Check(
|
||||
GetExitCodeProcess(process.hProcess, &exit_code) != 0 &&
|
||||
(exit_code == 322 || exit_code == EXCEPTION_NONCONTINUABLE_EXCEPTION),
|
||||
"death case did not use the PageManager fatal exit");
|
||||
CloseHandle(process.hThread);
|
||||
@@ -642,108 +318,42 @@ void CheckDeathCase(const char* name) {
|
||||
}
|
||||
int status = 0;
|
||||
Check(::waitpid(pid, &status, 0) == pid, "waitpid failed");
|
||||
// Exit status carries only the low 8 bits.
|
||||
const bool fatal_exit = WIFEXITED(status) && WEXITSTATUS(status) == (322 & 0xff);
|
||||
const bool fatal_exit =
|
||||
WIFEXITED(status) && WEXITSTATUS(status) == (322 & 0xff);
|
||||
const bool fatal_signal = WIFSIGNALED(status);
|
||||
Check(fatal_exit || fatal_signal, "death case did not use the PageManager fatal exit");
|
||||
Check(fatal_exit || fatal_signal,
|
||||
"death case did not use the PageManager fatal exit");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestFatalPaths() {
|
||||
for (const char* name:
|
||||
{"invalid-range", "unknown-untrack", "destructor-watch", "non-write", "callback-false",
|
||||
"reentrant", "cross-reentrant", "concurrent-non-write"}) {
|
||||
for (const char *name :
|
||||
{"invalid-range", "unknown-untrack", "destructor-watch"}) {
|
||||
CheckDeathCase(name);
|
||||
}
|
||||
}
|
||||
|
||||
void TestConcurrentFault() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
context.block = true;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
bool first_result = false;
|
||||
bool second_result = false;
|
||||
std::thread first([&] { first_result = manager.HandleFault(PageFaultAccess::Write, address); });
|
||||
while (!context.entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::thread second(
|
||||
[&] { second_result = manager.HandleFault(PageFaultAccess::Write, address); });
|
||||
context.release.store(true, std::memory_order_release);
|
||||
first.join();
|
||||
second.join();
|
||||
Check(first_result && second_result && context.calls.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent faults dispatched invalidation more than once");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestExternalDirtyTransferDuringResolution() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
context.block = true;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto* memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
bool handled = false;
|
||||
std::thread fault([&] { handled = manager.HandleFault(PageFaultAccess::Write, address); });
|
||||
while (!context.entered.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
manager.UpdatePageWatchers(false, address, page_size);
|
||||
context.release.store(true, std::memory_order_release);
|
||||
fault.join();
|
||||
Check(handled && !manager.IsTracked(address) && IsWritable(memory),
|
||||
"external dirty transfer did not satisfy active resolution");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::LibKernel::Memory {
|
||||
|
||||
bool ProtectGuestHostMemory(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) {
|
||||
bool ProtectGuestHostMemory(uint64_t vaddr, uint64_t size,
|
||||
Common::VirtualMemory::Mode mode) {
|
||||
return ProtectAddressSpace(vaddr, size, mode);
|
||||
}
|
||||
|
||||
} // namespace Libs::LibKernel::Memory
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if 1
|
||||
int main(int argc, char **argv) {
|
||||
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
|
||||
RunDeathCase(argv[2]);
|
||||
}
|
||||
TestWatchFaultAndUnwatch();
|
||||
TestSharedWatcherFault();
|
||||
TestReadWriteWatcherFault();
|
||||
TestPermittedMappedLateFaultsResume();
|
||||
TestPartialMappingUnmapPreservesTokens();
|
||||
TestNativeDelayedReadAfterModeDowngrade();
|
||||
TestDelayedFaultAfterExplicitUnwatch();
|
||||
TestNativeAccessViolation();
|
||||
TestWatchAndUnwatch();
|
||||
TestSharedWatcherCounts();
|
||||
TestMixedWatcherModes();
|
||||
TestCrossRegionRange();
|
||||
TestBatchedWatcherRanges();
|
||||
TestConcurrentFault();
|
||||
TestExternalDirtyTransferDuringResolution();
|
||||
TestFatalPaths();
|
||||
std::puts("PageManagerTests: all cases passed");
|
||||
return 0;
|
||||
#else
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
std::fputs("PageManagerTests: unsupported platform\n", stderr);
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3135,10 +3135,7 @@ public:
|
||||
Require(name, "partial image unmap tracking",
|
||||
partial_unmap_image_id &&
|
||||
!texture_cache.FindImageFromRange(partial_unmap_image.info.data.address,
|
||||
0x2000, false) &&
|
||||
!texture_cache.InvalidateMemory(
|
||||
PageFaultAccess::Write, partial_unmap_image.info.data.address + 0x1000,
|
||||
0x1000, PageFaultPhase::Invalidate),
|
||||
0x2000, false),
|
||||
"partial unmap left the deleted image's mapped tail tracked");
|
||||
|
||||
constexpr uint64_t unformatted_alias_offset = 0x2500000;
|
||||
@@ -14459,17 +14456,6 @@ void CheckEmbeddedFetchVertexOffset() {
|
||||
std::printf("[host] %-32s ok\n", "EmbeddedFetchVertexOffset");
|
||||
}
|
||||
|
||||
struct CacheFaultContext {
|
||||
TextureCache* texture = nullptr;
|
||||
};
|
||||
|
||||
bool CacheFault(void* opaque, PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
auto* context = static_cast<CacheFaultContext*>(opaque);
|
||||
return context != nullptr && context->texture != nullptr &&
|
||||
context->texture->InvalidateMemory(access, vaddr, size, phase);
|
||||
}
|
||||
|
||||
[[noreturn]] void RunReverseRenderTargetDeathCase() {
|
||||
(void)TextureGetRenderTargetFormat(12u, 7u, 3u);
|
||||
std::_Exit(0x7f);
|
||||
@@ -16110,7 +16096,7 @@ void CheckStorageTextureGpuOwnedRebindState() {
|
||||
auto* memory = reinterpret_cast<uint8_t*>(guest_memory);
|
||||
Require("StorageTextureGpuOwnedRebind", "allocation", guest_memory == base,
|
||||
"fixed guest-owner allocation failed");
|
||||
PageManager page_manager(CacheFault, nullptr);
|
||||
PageManager page_manager;
|
||||
MemoryTracker tracker(page_manager);
|
||||
page_manager.OnGpuMap(base, size);
|
||||
tracker.ForEachUploadRange(
|
||||
|
||||
Reference in New Issue
Block a user