2026-09-09 21:35:49 +02:00
// 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
2026-09-26 16:02:35 +02:00
# include <algorithm>
2026-09-09 21:35:49 +02:00
# 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 { } ;
2026-09-26 16:02:35 +02:00
static std : : mutex vector_regions_mutex { } ;
2026-09-09 21:35:49 +02:00
// 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 ;
2026-09-26 16:02:35 +02:00
u64 exception_addr = reinterpret_cast < u64 > ( info - > ExceptionRecord - > ExceptionInformation [ 1 ] ) ;
2026-09-09 21:35:49 +02:00
2026-09-26 16:02:35 +02:00
if ( code ! = EXCEPTION_ACCESS_VIOLATION | | info - > ExceptionRecord - > ExceptionInformation [ 0 ] = = 1 ) {
2026-09-09 21:35:49 +02:00
// Not our problem
return EXCEPTION_CONTINUE_SEARCH ;
}
u64 addr = 0 , addr2 = 0 ;
2026-09-26 16:02:35 +02:00
{
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 ;
}
2026-09-09 21:35:49 +02:00
}
}
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 ( ) ) ;
2026-09-26 16:02:35 +02:00
} 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 ;
2026-09-09 21:35:49 +02:00
} else if ( info . State ! = MEM_RESERVE ) {
2026-09-26 16:02:35 +02:00
LOG_ERROR ( HW_Memory , " Tried to commit an unreserved large buffer region at {:#x} that is not mapped (state {:#x}) " , addr , info . State ) ;
2026-09-09 21:35:49 +02:00
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
2026-09-23 13:52:29 +02:00
# 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
}
2026-09-09 21:35:49 +02:00
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 ) {
2026-09-26 16:02:35 +02:00
std : : lock_guard lock ( vector_regions_mutex ) ;
vector_regions . emplace_back ( reinterpret_cast < u64 > ( base ) > > HostPageBits , ( reinterpret_cast < u64 > ( base ) + size ) > > HostPageBits ) ;
2026-09-09 21:35:49 +02:00
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
2026-09-26 16:02:35 +02:00
std : : lock_guard lock ( vector_regions_mutex ) ;
std : : erase_if ( vector_regions , [ base ] ( const auto & r ) { return r . first = = reinterpret_cast < u64 > ( base ) ; } ) ;
2026-09-09 21:35:49 +02:00
ASSERT ( VirtualFree ( base , 0 , MEM_RELEASE ) ) ;
# else
ASSERT ( munmap ( base , size ) = = 0 ) ;
# endif
}
} // namespace Common