You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
6.1 KiB
174 lines
6.1 KiB
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* virtual_buffer.cpp */
|
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#ifdef _WIN32
|
|
#include <algorithm>
|
|
#include <windows.h>
|
|
#include <mutex>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#include "common/alignment.h"
|
|
#include "common/assert.h"
|
|
#include "common/sparse_large_vector.h"
|
|
|
|
namespace Common {
|
|
|
|
#ifdef _WIN32
|
|
static std::vector<std::pair<u64, u64>> vector_regions {};
|
|
static std::mutex vector_regions_mutex {};
|
|
|
|
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error
|
|
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
|
|
DWORD code = info->ExceptionRecord->ExceptionCode;
|
|
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionInformation[1]);
|
|
|
|
if (code != EXCEPTION_ACCESS_VIOLATION || info->ExceptionRecord->ExceptionInformation[0] == 1) {
|
|
// Not our problem
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
u64 addr = 0, addr2 = 0;
|
|
|
|
{
|
|
std::lock_guard lock(vector_regions_mutex);
|
|
for (auto region: vector_regions) {
|
|
auto addr_shifted = exception_addr >> HostPageBits;
|
|
if (region.first <= addr_shifted && addr_shifted <= region.second) {
|
|
addr = addr_shifted;
|
|
}
|
|
|
|
// Page-boundary accesses
|
|
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) {
|
|
addr2 = addr_;
|
|
}
|
|
|
|
if (addr != 0 || addr2 != 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (addr == 0 && addr2 == 0) {
|
|
// Not our problem
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr);
|
|
|
|
// Commit this region
|
|
if (addr != 0) {
|
|
if (!CommitVectorPage(addr << HostPageBits, false)) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
}
|
|
// Commit next region if needed
|
|
if (addr2 != 0) {
|
|
if (!CommitVectorPage(addr2 << HostPageBits, false)) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
|
MEMORY_BASIC_INFORMATION info {};
|
|
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
|
|
if (res == 0) {
|
|
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
|
|
} else if (info.State == MEM_COMMIT) {
|
|
DWORD old_protect {};
|
|
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
|
|
if (!VirtualProtect(reinterpret_cast<void*>(addr), HostPageSize, perm, &old_protect)) {
|
|
LOG_ERROR(HW_Memory, "Failed to change permissions of large buffer region at {:#x}, error {}", addr, GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
} else if (info.State != MEM_RESERVE) {
|
|
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped (state {:#x})", addr, info.State);
|
|
return false;
|
|
}
|
|
|
|
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
|
|
void* res2 = VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm);
|
|
if (res2 == nullptr) {
|
|
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#ifndef MAP_NOCORE
|
|
#define MAP_NOCORE 0
|
|
#endif
|
|
#ifndef MADV_FREE
|
|
#define MADV_FREE MADV_DONTNEED
|
|
#endif
|
|
|
|
void DecommitVectorPage(uintptr_t base) noexcept {
|
|
#if defined(_WIN32)
|
|
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
|
|
#elif defined(__linux__)
|
|
// Linux's MADV_DONTNEED zeros out pages for us
|
|
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
|
|
#else
|
|
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
|
|
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
|
#endif
|
|
}
|
|
|
|
void* AllocateMemoryPages(std::size_t size) noexcept {
|
|
if (auto page = HostPageSize; size % page != 0) {
|
|
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
|
|
size = AlignUp(size, page);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually
|
|
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
if (base != nullptr) {
|
|
std::lock_guard lock(vector_regions_mutex);
|
|
vector_regions.emplace_back(reinterpret_cast<u64>(base) >> HostPageBits, (reinterpret_cast<u64>(base) + size) >> HostPageBits);
|
|
|
|
static std::once_flag flag;
|
|
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
|
|
} else {
|
|
// Try committing everything instead??
|
|
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
|
|
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
|
}
|
|
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
|
|
#else
|
|
void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0);
|
|
if (base == MAP_FAILED)
|
|
base = nullptr;
|
|
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno));
|
|
#endif
|
|
return base;
|
|
}
|
|
|
|
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
|
|
if (auto page = HostPageSize; size % page != 0) {
|
|
size = AlignUp(size, page);
|
|
}
|
|
if (!base)
|
|
return;
|
|
#ifdef _WIN32
|
|
std::lock_guard lock(vector_regions_mutex);
|
|
std::erase_if(vector_regions, [base](const auto& r) {return r.first == reinterpret_cast<u64>(base); });
|
|
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
|
|
#else
|
|
ASSERT(munmap(base, size) == 0);
|
|
#endif
|
|
}
|
|
|
|
} // namespace Common
|