mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-26 19:11:00 +00:00
37fe911952
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- Adds mutex to prevent multiple threads from accessing the vector at the same time and corrects the Windows exception handler to use the correct faulting address used by the exception handler (previously used the instruction address instead of the fault address) and properly shifts the stored values for the handler. Fixes weird compiler-specific bugs on Windows Co-authored-by: bruno <protoxseven@gmail.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4481 Reviewed-by: lizzie <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
175 lines
6.1 KiB
C++
175 lines
6.1 KiB
C++
// 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
|