mirror of https://github.com/stenzek/duckstation
				
				
				
			Bus: 16KB page compatibility for mmap fastmem
							parent
							
								
									5bbb7cf906
								
							
						
					
					
						commit
						3c68543491
					
				@ -0,0 +1,400 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
 | 
			
		||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 | 
			
		||||
 | 
			
		||||
#include "memmap.h"
 | 
			
		||||
#include "align.h"
 | 
			
		||||
#include "assert.h"
 | 
			
		||||
#include "log.h"
 | 
			
		||||
#include "string_util.h"
 | 
			
		||||
 | 
			
		||||
#include "fmt/format.h"
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
#include "windows_headers.h"
 | 
			
		||||
#elif !defined(__ANDROID__)
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
Log_SetChannel(MemoryArena);
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
 | 
			
		||||
bool MemMap::MemProtect(void* baseaddr, size_t size, PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert((size & (HOST_PAGE_SIZE - 1)) == 0);
 | 
			
		||||
 | 
			
		||||
  DWORD old_protect;
 | 
			
		||||
  if (!VirtualProtect(baseaddr, size, static_cast<DWORD>(mode), &old_protect))
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("VirtualProtect() failed with error %u", GetLastError());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string MemMap::GetFileMappingName(const char* prefix)
 | 
			
		||||
{
 | 
			
		||||
  const unsigned pid = GetCurrentProcessId();
 | 
			
		||||
  return fmt::format("{}_{}", prefix, pid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemMap::CreateSharedMemory(const char* name, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  return static_cast<void*>(CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
 | 
			
		||||
                                               static_cast<DWORD>(size >> 32), static_cast<DWORD>(size),
 | 
			
		||||
                                               StringUtil::UTF8StringToWideString(name).c_str()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemMap::DestroySharedMemory(void* ptr)
 | 
			
		||||
{
 | 
			
		||||
  CloseHandle(static_cast<HANDLE>(ptr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemMap::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  void* ret = MapViewOfFileEx(static_cast<HANDLE>(handle), FILE_MAP_READ | FILE_MAP_WRITE,
 | 
			
		||||
                              static_cast<DWORD>(offset >> 32), static_cast<DWORD>(offset), size, baseaddr);
 | 
			
		||||
  if (!ret)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 | 
			
		||||
  if (mode != PageProtect::ReadWrite)
 | 
			
		||||
  {
 | 
			
		||||
    DWORD old_prot;
 | 
			
		||||
    if (!VirtualProtect(ret, size, static_cast<DWORD>(mode), &old_prot))
 | 
			
		||||
      Panic("Failed to protect memory mapping");
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemMap::UnmapSharedMemory(void* baseaddr, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  if (!UnmapViewOfFile(baseaddr))
 | 
			
		||||
    Panic("Failed to unmap shared memory");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SharedMemoryMappingArea::SharedMemoryMappingArea() = default;
 | 
			
		||||
 | 
			
		||||
SharedMemoryMappingArea::~SharedMemoryMappingArea()
 | 
			
		||||
{
 | 
			
		||||
  Destroy();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SharedMemoryMappingArea::PlaceholderMap::iterator SharedMemoryMappingArea::FindPlaceholder(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
  if (m_placeholder_ranges.empty())
 | 
			
		||||
    return m_placeholder_ranges.end();
 | 
			
		||||
 | 
			
		||||
  // this will give us an iterator equal or after page
 | 
			
		||||
  auto it = m_placeholder_ranges.lower_bound(offset);
 | 
			
		||||
  if (it == m_placeholder_ranges.end())
 | 
			
		||||
  {
 | 
			
		||||
    // check the last page
 | 
			
		||||
    it = (++m_placeholder_ranges.rbegin()).base();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // it's the one we found?
 | 
			
		||||
  if (offset >= it->first && offset < it->second)
 | 
			
		||||
    return it;
 | 
			
		||||
 | 
			
		||||
  // otherwise try the one before
 | 
			
		||||
  if (it == m_placeholder_ranges.begin())
 | 
			
		||||
    return m_placeholder_ranges.end();
 | 
			
		||||
 | 
			
		||||
  --it;
 | 
			
		||||
  if (offset >= it->first && offset < it->second)
 | 
			
		||||
    return it;
 | 
			
		||||
  else
 | 
			
		||||
    return m_placeholder_ranges.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SharedMemoryMappingArea::Create(size_t size)
 | 
			
		||||
{
 | 
			
		||||
  Destroy();
 | 
			
		||||
 | 
			
		||||
  AssertMsg(Common::IsAlignedPow2(size, HOST_PAGE_SIZE), "Size is page aligned");
 | 
			
		||||
 | 
			
		||||
  m_base_ptr = static_cast<u8*>(VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
 | 
			
		||||
                                              PAGE_NOACCESS, nullptr, 0));
 | 
			
		||||
  if (!m_base_ptr)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_num_pages = size / HOST_PAGE_SIZE;
 | 
			
		||||
  m_placeholder_ranges.emplace(0, size);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SharedMemoryMappingArea::Destroy()
 | 
			
		||||
{
 | 
			
		||||
  AssertMsg(m_num_mappings == 0, "No mappings left");
 | 
			
		||||
 | 
			
		||||
  // hopefully this will be okay, and we don't need to coalesce all the placeholders...
 | 
			
		||||
  if (m_base_ptr && !VirtualFreeEx(GetCurrentProcess(), m_base_ptr, 0, MEM_RELEASE))
 | 
			
		||||
    Panic("Failed to release shared memory area");
 | 
			
		||||
 | 
			
		||||
  m_placeholder_ranges.clear();
 | 
			
		||||
  m_base_ptr = nullptr;
 | 
			
		||||
  m_size = 0;
 | 
			
		||||
  m_num_pages = 0;
 | 
			
		||||
  m_num_mappings = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size,
 | 
			
		||||
                                 PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
 | 
			
		||||
 | 
			
		||||
  const size_t map_offset = static_cast<u8*>(map_base) - m_base_ptr;
 | 
			
		||||
  DebugAssert(Common::IsAlignedPow2(map_offset, HOST_PAGE_SIZE));
 | 
			
		||||
  DebugAssert(Common::IsAlignedPow2(map_size, HOST_PAGE_SIZE));
 | 
			
		||||
 | 
			
		||||
  // should be a placeholder. unless there's some other mapping we didn't free.
 | 
			
		||||
  PlaceholderMap::iterator phit = FindPlaceholder(map_offset);
 | 
			
		||||
  DebugAssertMsg(phit != m_placeholder_ranges.end(), "Page we're mapping is a placeholder");
 | 
			
		||||
  DebugAssertMsg(map_offset >= phit->first && map_offset < phit->second, "Page is in returned placeholder range");
 | 
			
		||||
  DebugAssertMsg((map_offset + map_size) <= phit->second, "Page range is in returned placeholder range");
 | 
			
		||||
 | 
			
		||||
  // do we need to split to the left? (i.e. is there a placeholder before this range)
 | 
			
		||||
  const size_t old_ph_end = phit->second;
 | 
			
		||||
  if (map_offset != phit->first)
 | 
			
		||||
  {
 | 
			
		||||
    phit->second = map_offset;
 | 
			
		||||
 | 
			
		||||
    // split it (i.e. left..start and start..end are now separated)
 | 
			
		||||
    if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(phit->first), (map_offset - phit->first),
 | 
			
		||||
                       MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
 | 
			
		||||
    {
 | 
			
		||||
      Panic("Failed to left split placeholder for map");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    // start of the placeholder is getting used, we'll split it right below if there's anything left over
 | 
			
		||||
    m_placeholder_ranges.erase(phit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // do we need to split to the right? (i.e. is there a placeholder after this range)
 | 
			
		||||
  if ((map_offset + map_size) != old_ph_end)
 | 
			
		||||
  {
 | 
			
		||||
    // split out end..ph_end
 | 
			
		||||
    m_placeholder_ranges.emplace(map_offset + map_size, old_ph_end);
 | 
			
		||||
 | 
			
		||||
    if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(map_offset), map_size,
 | 
			
		||||
                       MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
 | 
			
		||||
    {
 | 
			
		||||
      Panic("Failed to right split placeholder for map");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // actually do the mapping, replacing the placeholder on the range
 | 
			
		||||
  if (!MapViewOfFile3(static_cast<HANDLE>(file_handle), GetCurrentProcess(), map_base, file_offset, map_size,
 | 
			
		||||
                      MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0))
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("MapViewOfFile3() failed: %u", GetLastError());
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (mode != PageProtect::ReadWrite)
 | 
			
		||||
  {
 | 
			
		||||
    DWORD old_prot;
 | 
			
		||||
    if (!VirtualProtect(map_base, map_size, static_cast<DWORD>(mode), &old_prot))
 | 
			
		||||
      Panic("Failed to protect memory mapping");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_num_mappings++;
 | 
			
		||||
  return static_cast<u8*>(map_base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
 | 
			
		||||
 | 
			
		||||
  const size_t map_offset = static_cast<u8*>(map_base) - m_base_ptr;
 | 
			
		||||
  DebugAssert(Common::IsAlignedPow2(map_offset, HOST_PAGE_SIZE));
 | 
			
		||||
  DebugAssert(Common::IsAlignedPow2(map_size, HOST_PAGE_SIZE));
 | 
			
		||||
 | 
			
		||||
  // unmap the specified range
 | 
			
		||||
  if (!UnmapViewOfFile2(GetCurrentProcess(), map_base, MEM_PRESERVE_PLACEHOLDER))
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("UnmapViewOfFile2() failed: %u", GetLastError());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // can we coalesce to the left?
 | 
			
		||||
  PlaceholderMap::iterator left_it = (map_offset > 0) ? FindPlaceholder(map_offset - 1) : m_placeholder_ranges.end();
 | 
			
		||||
  if (left_it != m_placeholder_ranges.end())
 | 
			
		||||
  {
 | 
			
		||||
    // the left placeholder should end at our start
 | 
			
		||||
    DebugAssert(map_offset == left_it->second);
 | 
			
		||||
    left_it->second = map_offset + map_size;
 | 
			
		||||
 | 
			
		||||
    // combine placeholders before and the range we're unmapping, i.e. to the left
 | 
			
		||||
    if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first), left_it->second - left_it->first,
 | 
			
		||||
                       MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
 | 
			
		||||
    {
 | 
			
		||||
      Panic("Failed to coalesce placeholders left for unmap");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    // this is a new placeholder
 | 
			
		||||
    left_it = m_placeholder_ranges.emplace(map_offset, map_offset + map_size).first;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // can we coalesce to the right?
 | 
			
		||||
  PlaceholderMap::iterator right_it =
 | 
			
		||||
    ((map_offset + map_size) < m_size) ? FindPlaceholder(map_offset + map_size) : m_placeholder_ranges.end();
 | 
			
		||||
  if (right_it != m_placeholder_ranges.end())
 | 
			
		||||
  {
 | 
			
		||||
    // should start at our end
 | 
			
		||||
    DebugAssert(right_it->first == (map_offset + map_size));
 | 
			
		||||
    left_it->second = right_it->second;
 | 
			
		||||
    m_placeholder_ranges.erase(right_it);
 | 
			
		||||
 | 
			
		||||
    // combine our placeholder and the next, i.e. to the right
 | 
			
		||||
    if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first), left_it->second - left_it->first,
 | 
			
		||||
                       MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
 | 
			
		||||
    {
 | 
			
		||||
      Panic("Failed to coalescae placeholders right for unmap");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_num_mappings--;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
bool MemMap::MemProtect(void* baseaddr, size_t size, PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssertMsg((size & (HOST_PAGE_SIZE - 1)) == 0, "Size is page aligned");
 | 
			
		||||
 | 
			
		||||
  const int result = mprotect(baseaddr, size, static_cast<int>(mode));
 | 
			
		||||
  if (result != 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("mprotect() for %zu at %p failed", size, baseaddr);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string MemMap::GetFileMappingName(const char* prefix)
 | 
			
		||||
{
 | 
			
		||||
  const unsigned pid = static_cast<unsigned>(getpid());
 | 
			
		||||
#if defined(__FreeBSD__)
 | 
			
		||||
  // FreeBSD's shm_open(3) requires name to be absolute
 | 
			
		||||
  return fmt::format("/tmp/{}_{}", prefix, pid);
 | 
			
		||||
#else
 | 
			
		||||
  return fmt::format("{}_{}", prefix, pid);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemMap::CreateSharedMemory(const char* name, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  const int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
 | 
			
		||||
  if (fd < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("shm_open failed: %d\n", errno);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // we're not going to be opening this mapping in other processes, so remove the file
 | 
			
		||||
  shm_unlink(name);
 | 
			
		||||
 | 
			
		||||
  // ensure it's the correct size
 | 
			
		||||
  if (ftruncate(fd, static_cast<off_t>(size)) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("ftruncate(%zu) failed: %d\n", size, errno);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return reinterpret_cast<void*>(static_cast<intptr_t>(fd));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemMap::DestroySharedMemory(void* ptr)
 | 
			
		||||
{
 | 
			
		||||
  close(static_cast<int>(reinterpret_cast<intptr_t>(ptr)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemMap::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  const int flags = (baseaddr != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED;
 | 
			
		||||
  void* ptr = mmap(baseaddr, size, static_cast<int>(mode), flags, static_cast<int>(reinterpret_cast<intptr_t>(handle)),
 | 
			
		||||
                   static_cast<off_t>(offset));
 | 
			
		||||
  if (ptr == MAP_FAILED)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 | 
			
		||||
  return ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemMap::UnmapSharedMemory(void* baseaddr, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  if (mmap(baseaddr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED)
 | 
			
		||||
    Panic("Failed to unmap shared memory");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SharedMemoryMappingArea::SharedMemoryMappingArea() = default;
 | 
			
		||||
 | 
			
		||||
SharedMemoryMappingArea::~SharedMemoryMappingArea()
 | 
			
		||||
{
 | 
			
		||||
  Destroy();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SharedMemoryMappingArea::Create(size_t size)
 | 
			
		||||
{
 | 
			
		||||
  AssertMsg(Common::IsAlignedPow2(size, HOST_PAGE_SIZE), "Size is page aligned");
 | 
			
		||||
  Destroy();
 | 
			
		||||
 | 
			
		||||
  void* alloc = mmap(nullptr, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 | 
			
		||||
  if (alloc == MAP_FAILED)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  m_base_ptr = static_cast<u8*>(alloc);
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_num_pages = size / HOST_PAGE_SIZE;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SharedMemoryMappingArea::Destroy()
 | 
			
		||||
{
 | 
			
		||||
  AssertMsg(m_num_mappings == 0, "No mappings left");
 | 
			
		||||
 | 
			
		||||
  if (m_base_ptr && munmap(m_base_ptr, m_size) != 0)
 | 
			
		||||
    Panic("Failed to release shared memory area");
 | 
			
		||||
 | 
			
		||||
  m_base_ptr = nullptr;
 | 
			
		||||
  m_size = 0;
 | 
			
		||||
  m_num_pages = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size,
 | 
			
		||||
                                 PageProtect mode)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
 | 
			
		||||
 | 
			
		||||
  void* const ptr = mmap(map_base, map_size, static_cast<int>(mode), MAP_SHARED | MAP_FIXED,
 | 
			
		||||
                         static_cast<int>(reinterpret_cast<intptr_t>(file_handle)), static_cast<off_t>(file_offset));
 | 
			
		||||
  if (ptr == MAP_FAILED)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 | 
			
		||||
  m_num_mappings++;
 | 
			
		||||
  return static_cast<u8*>(ptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
 | 
			
		||||
{
 | 
			
		||||
  DebugAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
 | 
			
		||||
 | 
			
		||||
  if (mmap(map_base, map_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  m_num_mappings--;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
 | 
			
		||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "types.h"
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
 | 
			
		||||
// eww :/ but better than including windows.h
 | 
			
		||||
enum class PageProtect : u32
 | 
			
		||||
{
 | 
			
		||||
  NoAccess = 0x01,         // PAGE_NOACCESS
 | 
			
		||||
  ReadOnly = 0x02,         // PAGE_READONLY
 | 
			
		||||
  ReadWrite = 0x04,        // PAGE_READWRITE
 | 
			
		||||
  ReadExecute = 0x20,      // PAGE_EXECUTE_READ
 | 
			
		||||
  ReadWriteExecute = 0x40, // PAGE_EXECUTE_READWRITE
 | 
			
		||||
};
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
 | 
			
		||||
enum class PageProtect : u32
 | 
			
		||||
{
 | 
			
		||||
  NoAccess = PROT_NONE,
 | 
			
		||||
  ReadOnly = PROT_READ,
 | 
			
		||||
  ReadWrite = PROT_READ | PROT_WRITE,
 | 
			
		||||
  ReadExecute = PROT_READ | PROT_EXEC,
 | 
			
		||||
  ReadWriteExecute = PROT_READ | PROT_WRITE | PROT_EXEC,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace MemMap {
 | 
			
		||||
std::string GetFileMappingName(const char* prefix);
 | 
			
		||||
void* CreateSharedMemory(const char* name, size_t size);
 | 
			
		||||
void DestroySharedMemory(void* ptr);
 | 
			
		||||
void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, PageProtect mode);
 | 
			
		||||
void UnmapSharedMemory(void* baseaddr, size_t size);
 | 
			
		||||
bool MemProtect(void* baseaddr, size_t size, PageProtect mode);
 | 
			
		||||
} // namespace MemMap
 | 
			
		||||
 | 
			
		||||
class SharedMemoryMappingArea
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  SharedMemoryMappingArea();
 | 
			
		||||
  ~SharedMemoryMappingArea();
 | 
			
		||||
 | 
			
		||||
  ALWAYS_INLINE size_t GetSize() const { return m_size; }
 | 
			
		||||
  ALWAYS_INLINE size_t GetNumPages() const { return m_num_pages; }
 | 
			
		||||
 | 
			
		||||
  ALWAYS_INLINE u8* BasePointer() const { return m_base_ptr; }
 | 
			
		||||
  ALWAYS_INLINE u8* OffsetPointer(size_t offset) const { return m_base_ptr + offset; }
 | 
			
		||||
  ALWAYS_INLINE u8* PagePointer(size_t page) const { return m_base_ptr + HOST_PAGE_SIZE * page; }
 | 
			
		||||
 | 
			
		||||
  bool Create(size_t size);
 | 
			
		||||
  void Destroy();
 | 
			
		||||
 | 
			
		||||
  u8* Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size, PageProtect mode);
 | 
			
		||||
  bool Unmap(void* map_base, size_t map_size);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  u8* m_base_ptr = nullptr;
 | 
			
		||||
  size_t m_size = 0;
 | 
			
		||||
  size_t m_num_pages = 0;
 | 
			
		||||
  size_t m_num_mappings = 0;
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
  using PlaceholderMap = std::map<size_t, size_t>;
 | 
			
		||||
 | 
			
		||||
  PlaceholderMap::iterator FindPlaceholder(size_t page);
 | 
			
		||||
 | 
			
		||||
  PlaceholderMap m_placeholder_ranges;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
@ -1,400 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
 | 
			
		||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 | 
			
		||||
 | 
			
		||||
#include "memory_arena.h"
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/log.h"
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
Log_SetChannel(Common::MemoryArena);
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
#include "common/windows_headers.h"
 | 
			
		||||
#elif defined(ANDROID)
 | 
			
		||||
#include <dlfcn.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <linux/ashmem.h>
 | 
			
		||||
#include <sys/ioctl.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace Common {
 | 
			
		||||
 | 
			
		||||
// Borrowed from Dolphin
 | 
			
		||||
#ifdef ANDROID
 | 
			
		||||
#define ASHMEM_DEVICE "/dev/ashmem"
 | 
			
		||||
 | 
			
		||||
static int AshmemCreateFileMapping(const char* name, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  // ASharedMemory path - works on API >= 26 and falls through on API < 26:
 | 
			
		||||
 | 
			
		||||
  // We can't call ASharedMemory_create the normal way without increasing the
 | 
			
		||||
  // minimum version requirement to API 26, so we use dlopen/dlsym instead
 | 
			
		||||
  static void* libandroid = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
 | 
			
		||||
  static auto shared_memory_create =
 | 
			
		||||
    reinterpret_cast<int (*)(const char*, size_t)>(dlsym(libandroid, "ASharedMemory_create"));
 | 
			
		||||
  if (shared_memory_create)
 | 
			
		||||
    return shared_memory_create(name, size);
 | 
			
		||||
 | 
			
		||||
  // /dev/ashmem path - works on API < 29:
 | 
			
		||||
 | 
			
		||||
  int fd, ret;
 | 
			
		||||
  fd = open(ASHMEM_DEVICE, O_RDWR);
 | 
			
		||||
  if (fd < 0)
 | 
			
		||||
    return fd;
 | 
			
		||||
 | 
			
		||||
  // We don't really care if we can't set the name, it is optional
 | 
			
		||||
  ioctl(fd, ASHMEM_SET_NAME, name);
 | 
			
		||||
 | 
			
		||||
  ret = ioctl(fd, ASHMEM_SET_SIZE, size);
 | 
			
		||||
  if (ret < 0)
 | 
			
		||||
  {
 | 
			
		||||
    close(fd);
 | 
			
		||||
    Log_ErrorPrintf("Ashmem returned error: 0x%08x", ret);
 | 
			
		||||
    return ret;
 | 
			
		||||
  }
 | 
			
		||||
  return fd;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
MemoryArena::MemoryArena() = default;
 | 
			
		||||
 | 
			
		||||
MemoryArena::~MemoryArena()
 | 
			
		||||
{
 | 
			
		||||
  Destroy();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemoryArena::FindBaseAddressForMapping(size_t size)
 | 
			
		||||
{
 | 
			
		||||
  void* base_address;
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  base_address = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
 | 
			
		||||
  if (base_address)
 | 
			
		||||
    VirtualFree(base_address, 0, MEM_RELEASE);
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
 | 
			
		||||
  if (base_address)
 | 
			
		||||
    munmap(base_address, size);
 | 
			
		||||
#elif defined(__ANDROID__)
 | 
			
		||||
  base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_SHARED, -1, 0);
 | 
			
		||||
  if (base_address)
 | 
			
		||||
    munmap(base_address, size);
 | 
			
		||||
#else
 | 
			
		||||
  base_address = nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!base_address)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("Failed to get base address for memory mapping of size %zu", size);
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return base_address;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::IsValid() const
 | 
			
		||||
{
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  return m_file_handle != nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return m_shmem_fd >= 0;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string GetFileMappingName()
 | 
			
		||||
{
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  const unsigned pid = GetCurrentProcessId();
 | 
			
		||||
#elif defined(__ANDROID__) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  const unsigned pid = static_cast<unsigned>(getpid());
 | 
			
		||||
#else
 | 
			
		||||
#error Unknown platform.
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  const std::string ret(StringUtil::StdStringFromFormat("duckstation_%u", pid));
 | 
			
		||||
  Log_InfoPrintf("File mapping name: %s", ret.c_str());
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::Create(size_t size, bool writable, bool executable)
 | 
			
		||||
{
 | 
			
		||||
  if (IsValid())
 | 
			
		||||
    Destroy();
 | 
			
		||||
 | 
			
		||||
  const std::string file_mapping_name(GetFileMappingName());
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  const DWORD protect = (writable ? (executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE) : PAGE_READONLY);
 | 
			
		||||
  m_file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, protect, Truncate32(size >> 32), Truncate32(size),
 | 
			
		||||
                                     file_mapping_name.c_str());
 | 
			
		||||
  if (!m_file_handle)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("CreateFileMapping failed: %u", GetLastError());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_writable = writable;
 | 
			
		||||
  m_executable = executable;
 | 
			
		||||
  return true;
 | 
			
		||||
#elif defined(__ANDROID__)
 | 
			
		||||
  m_shmem_fd = AshmemCreateFileMapping(file_mapping_name.c_str(), size);
 | 
			
		||||
  if (m_shmem_fd < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("AshmemCreateFileMapping failed: %d %d", m_shmem_fd, errno);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_writable = writable;
 | 
			
		||||
  m_executable = executable;
 | 
			
		||||
  return true;
 | 
			
		||||
#elif defined(__linux__)
 | 
			
		||||
  m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600);
 | 
			
		||||
  if (m_shmem_fd < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("shm_open failed: %d", errno);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // we're not going to be opening this mapping in other processes, so remove the file
 | 
			
		||||
  shm_unlink(file_mapping_name.c_str());
 | 
			
		||||
 | 
			
		||||
  // ensure it's the correct size
 | 
			
		||||
  if (ftruncate64(m_shmem_fd, static_cast<off64_t>(size)) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("ftruncate64(%zu) failed: %d", size, errno);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_writable = writable;
 | 
			
		||||
  m_executable = executable;
 | 
			
		||||
  return true;
 | 
			
		||||
#elif defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
#if defined(__APPLE__)
 | 
			
		||||
  m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600);
 | 
			
		||||
#else
 | 
			
		||||
  m_shmem_fd = shm_open(SHM_ANON, O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (m_shmem_fd < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("shm_open failed: %d", errno);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
  // we're not going to be opening this mapping in other processes, so remove the file
 | 
			
		||||
  shm_unlink(file_mapping_name.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // ensure it's the correct size
 | 
			
		||||
  if (ftruncate(m_shmem_fd, static_cast<off_t>(size)) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("ftruncate(%zu) failed: %d", size, errno);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_size = size;
 | 
			
		||||
  m_writable = writable;
 | 
			
		||||
  m_executable = executable;
 | 
			
		||||
  return true;
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemoryArena::Destroy()
 | 
			
		||||
{
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  if (m_file_handle)
 | 
			
		||||
  {
 | 
			
		||||
    CloseHandle(m_file_handle);
 | 
			
		||||
    m_file_handle = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
#elif defined(__linux__) || defined(__FreeBSD__)
 | 
			
		||||
  if (m_shmem_fd > 0)
 | 
			
		||||
  {
 | 
			
		||||
    close(m_shmem_fd);
 | 
			
		||||
    m_shmem_fd = -1;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<MemoryArena::View> MemoryArena::CreateView(size_t offset, size_t size, bool writable, bool executable,
 | 
			
		||||
                                                         void* fixed_address)
 | 
			
		||||
{
 | 
			
		||||
  void* base_pointer = CreateViewPtr(offset, size, writable, executable, fixed_address);
 | 
			
		||||
  if (!base_pointer)
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
 | 
			
		||||
  return View(this, base_pointer, offset, size, writable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<MemoryArena::View> MemoryArena::CreateReservedView(size_t size, void* fixed_address /*= nullptr*/)
 | 
			
		||||
{
 | 
			
		||||
  void* base_pointer = CreateReservedPtr(size, fixed_address);
 | 
			
		||||
  if (!base_pointer)
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
 | 
			
		||||
  return View(this, base_pointer, View::RESERVED_REGION_OFFSET, size, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemoryArena::CreateViewPtr(size_t offset, size_t size, bool writable, bool executable,
 | 
			
		||||
                                 void* fixed_address /*= nullptr*/)
 | 
			
		||||
{
 | 
			
		||||
  void* base_pointer;
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  const DWORD desired_access = FILE_MAP_READ | (writable ? FILE_MAP_WRITE : 0) | (executable ? FILE_MAP_EXECUTE : 0);
 | 
			
		||||
  base_pointer =
 | 
			
		||||
    MapViewOfFileEx(m_file_handle, desired_access, Truncate32(offset >> 32), Truncate32(offset), size, fixed_address);
 | 
			
		||||
  if (!base_pointer)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  const int flags = (fixed_address != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED;
 | 
			
		||||
  const int prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0);
 | 
			
		||||
  base_pointer = mmap(fixed_address, size, prot, flags, m_shmem_fd, static_cast<off_t>(offset));
 | 
			
		||||
  if (base_pointer == reinterpret_cast<void*>(-1))
 | 
			
		||||
    return nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  m_num_views.fetch_add(1);
 | 
			
		||||
  return base_pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::FlushViewPtr(void* address, size_t size)
 | 
			
		||||
{
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  return FlushViewOfFile(address, size);
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  return (msync(address, size, 0) >= 0);
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::ReleaseViewPtr(void* address, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  bool result;
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  result = static_cast<bool>(UnmapViewOfFile(address));
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  result = (munmap(address, size) >= 0);
 | 
			
		||||
#else
 | 
			
		||||
  result = false;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!result)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("Failed to unmap previously-created view at %p", address);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size_t prev_count = m_num_views.fetch_sub(1);
 | 
			
		||||
  Assert(prev_count > 0);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* MemoryArena::CreateReservedPtr(size_t size, void* fixed_address /*= nullptr*/)
 | 
			
		||||
{
 | 
			
		||||
  void* base_pointer;
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  base_pointer = VirtualAlloc(fixed_address, size, MEM_RESERVE, PAGE_NOACCESS);
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  const int flags =
 | 
			
		||||
    (fixed_address != nullptr) ? (MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED) : (MAP_PRIVATE | MAP_ANONYMOUS);
 | 
			
		||||
  base_pointer = mmap(fixed_address, size, PROT_NONE, flags, -1, 0);
 | 
			
		||||
  if (base_pointer == reinterpret_cast<void*>(-1))
 | 
			
		||||
    return nullptr;
 | 
			
		||||
#else
 | 
			
		||||
  return nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  m_num_views.fetch_add(1);
 | 
			
		||||
  return base_pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::ReleaseReservedPtr(void* address, size_t size)
 | 
			
		||||
{
 | 
			
		||||
  bool result;
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  result = static_cast<bool>(VirtualFree(address, 0, MEM_RELEASE));
 | 
			
		||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  result = (munmap(address, size) >= 0);
 | 
			
		||||
#else
 | 
			
		||||
  result = false;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (!result)
 | 
			
		||||
  {
 | 
			
		||||
    Log_ErrorPrintf("Failed to release previously-created view at %p", address);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size_t prev_count = m_num_views.fetch_sub(1);
 | 
			
		||||
  Assert(prev_count > 0);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemoryArena::SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable)
 | 
			
		||||
{
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  static constexpr DWORD protection_table[2][2][2] = {
 | 
			
		||||
    {{PAGE_NOACCESS, PAGE_EXECUTE}, {PAGE_WRITECOPY, PAGE_EXECUTE_WRITECOPY}},
 | 
			
		||||
    {{PAGE_READONLY, PAGE_EXECUTE_READ}, {PAGE_READWRITE, PAGE_EXECUTE_READWRITE}}};
 | 
			
		||||
 | 
			
		||||
  DWORD old_protect;
 | 
			
		||||
  return static_cast<bool>(
 | 
			
		||||
    VirtualProtect(address, length, protection_table[readable][writable][executable], &old_protect));
 | 
			
		||||
#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  const int prot = (readable ? PROT_READ : 0) | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0);
 | 
			
		||||
  return (mprotect(address, length, prot) >= 0);
 | 
			
		||||
#else
 | 
			
		||||
  return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MemoryArena::View::View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size,
 | 
			
		||||
                        bool writable)
 | 
			
		||||
  : m_parent(parent), m_base_pointer(base_pointer), m_arena_offset(arena_offset), m_mapping_size(mapping_size),
 | 
			
		||||
    m_writable(writable)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MemoryArena::View::View(View&& view)
 | 
			
		||||
  : m_parent(view.m_parent), m_base_pointer(view.m_base_pointer), m_arena_offset(view.m_arena_offset),
 | 
			
		||||
    m_mapping_size(view.m_mapping_size)
 | 
			
		||||
{
 | 
			
		||||
  view.m_parent = nullptr;
 | 
			
		||||
  view.m_base_pointer = nullptr;
 | 
			
		||||
  view.m_arena_offset = 0;
 | 
			
		||||
  view.m_mapping_size = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MemoryArena::View::~View()
 | 
			
		||||
{
 | 
			
		||||
  if (m_parent)
 | 
			
		||||
  {
 | 
			
		||||
    if (m_arena_offset != RESERVED_REGION_OFFSET)
 | 
			
		||||
    {
 | 
			
		||||
      if (m_writable && !m_parent->FlushViewPtr(m_base_pointer, m_mapping_size))
 | 
			
		||||
        Panic("Failed to flush previously-created view");
 | 
			
		||||
      if (!m_parent->ReleaseViewPtr(m_base_pointer, m_mapping_size))
 | 
			
		||||
        Panic("Failed to unmap previously-created view");
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      if (!m_parent->ReleaseReservedPtr(m_base_pointer, m_mapping_size))
 | 
			
		||||
        Panic("Failed to release previously-created view");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
} // namespace Common
 | 
			
		||||
@ -1,77 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
 | 
			
		||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "common/types.h"
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
namespace Common {
 | 
			
		||||
class MemoryArena
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  class View
 | 
			
		||||
  {
 | 
			
		||||
  public:
 | 
			
		||||
    enum : size_t
 | 
			
		||||
    {
 | 
			
		||||
      RESERVED_REGION_OFFSET = static_cast<size_t>(-1)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size, bool writable);
 | 
			
		||||
    View(View&& view);
 | 
			
		||||
    ~View();
 | 
			
		||||
 | 
			
		||||
    void* GetBasePointer() const { return m_base_pointer; }
 | 
			
		||||
    size_t GetArenaOffset() const { return m_arena_offset; }
 | 
			
		||||
    size_t GetMappingSize() const { return m_mapping_size; }
 | 
			
		||||
    bool IsWritable() const { return m_writable; }
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    MemoryArena* m_parent;
 | 
			
		||||
    void* m_base_pointer;
 | 
			
		||||
    size_t m_arena_offset;
 | 
			
		||||
    size_t m_mapping_size;
 | 
			
		||||
    bool m_writable;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  MemoryArena();
 | 
			
		||||
  ~MemoryArena();
 | 
			
		||||
 | 
			
		||||
  static void* FindBaseAddressForMapping(size_t size);
 | 
			
		||||
 | 
			
		||||
  ALWAYS_INLINE size_t GetSize() const { return m_size; }
 | 
			
		||||
  ALWAYS_INLINE bool IsWritable() const { return m_writable; }
 | 
			
		||||
  ALWAYS_INLINE bool IsExecutable() const { return m_executable; }
 | 
			
		||||
 | 
			
		||||
  bool IsValid() const;
 | 
			
		||||
  bool Create(size_t size, bool writable, bool executable);
 | 
			
		||||
  void Destroy();
 | 
			
		||||
 | 
			
		||||
  std::optional<View> CreateView(size_t offset, size_t size, bool writable, bool executable,
 | 
			
		||||
                                 void* fixed_address = nullptr);
 | 
			
		||||
 | 
			
		||||
  std::optional<View> CreateReservedView(size_t size,  void* fixed_address = nullptr);
 | 
			
		||||
 | 
			
		||||
  void* CreateViewPtr(size_t offset, size_t size, bool writable, bool executable, void* fixed_address = nullptr);
 | 
			
		||||
  bool FlushViewPtr(void* address, size_t size);
 | 
			
		||||
  bool ReleaseViewPtr(void* address, size_t size);
 | 
			
		||||
 | 
			
		||||
  void* CreateReservedPtr(size_t size, void* fixed_address = nullptr);
 | 
			
		||||
  bool ReleaseReservedPtr(void* address, size_t size);
 | 
			
		||||
 | 
			
		||||
  static bool SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
  void* m_file_handle = nullptr;
 | 
			
		||||
#elif defined(__linux__) || defined(ANDROID) || defined(__APPLE__) || defined(__FreeBSD__)
 | 
			
		||||
  int m_shmem_fd = -1;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  std::atomic_size_t m_num_views{0};
 | 
			
		||||
  size_t m_size = 0;
 | 
			
		||||
  bool m_writable = false;
 | 
			
		||||
  bool m_executable = false;
 | 
			
		||||
};
 | 
			
		||||
} // namespace Common
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue