rx/mem: cross platform implementation
Some checks are pending
Formatting check / formatting-check (push) Waiting to run
Build RPCSX / build-linux (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.1-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.2-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.4-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv8.5-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv9-a) (push) Waiting to run
Build RPCSX / build-android (arm64-v8a, armv9.1-a) (push) Waiting to run
Build RPCSX / build-android (x86_64, x86-64) (push) Waiting to run

fixed compilation errors
avoid memfd_create usage on android
This commit is contained in:
DH 2025-10-16 21:17:37 +03:00
parent 0bc167ea87
commit 3ffece2d77
10 changed files with 293 additions and 163 deletions

View file

@ -2,21 +2,11 @@
#include "AddressRange.hpp"
#include "EnumBitSet.hpp"
#include "mem.hpp"
#include <system_error>
#include <utility>
namespace rx {
enum class Protection {
R,
W,
X,
bitset_last = X
};
std::errc reserveVirtualSpace(rx::AddressRange range);
std::errc releaseVirtualSpace(rx::AddressRange range, std::size_t alignment);
class Mappable {
#ifdef _WIN32
using NativeHandle = void *;
@ -46,7 +36,8 @@ public:
static std::pair<Mappable, std::errc> CreateMemory(std::size_t size);
static std::pair<Mappable, std::errc> CreateSwap(std::size_t size);
std::errc map(rx::AddressRange virtualRange, std::size_t offset,
rx::EnumBitSet<Protection> protection, std::size_t alignment);
rx::EnumBitSet<mem::Protection> protection,
std::size_t alignment);
[[nodiscard]] NativeHandle release() {
return std::exchange(m_handle, kInvalidHandle);

View file

@ -1,14 +1,31 @@
#pragma once
#include "AddressRange.hpp"
#include "EnumBitSet.hpp"
#include <cstddef>
#include <system_error>
#include <utility>
namespace rx::mem {
enum class Protection {
R,
W,
X,
bitset_last = X
};
struct VirtualQueryEntry : rx::AddressRange {
rx::EnumBitSet<Protection> flags{};
VirtualQueryEntry() = default;
VirtualQueryEntry(rx::AddressRange range, rx::EnumBitSet<Protection> prot)
: AddressRange(range), flags(prot) {}
};
extern const std::size_t pageSize;
void *map(void *address, std::size_t size, int prot, int flags, int fd = -1,
std::ptrdiff_t offset = 0);
void *reserve(std::size_t size);
bool reserve(void *address, std::size_t size);
bool protect(void *address, std::size_t size, int prot);
bool unmap(void *address, std::size_t size);
void printStats();
std::errc reserve(rx::AddressRange range);
std::errc release(rx::AddressRange range, std::size_t alignment);
std::errc protect(rx::AddressRange range, rx::EnumBitSet<Protection> prot);
std::vector<VirtualQueryEntry> query(rx::AddressRange range);
} // namespace rx::mem

View file

@ -1,4 +1,5 @@
#include "Mappable.hpp"
#include "mem.hpp"
#include <system_error>
#ifndef _WIN32
@ -13,59 +14,10 @@
#include <windows.h>
#endif
std::errc rx::reserveVirtualSpace(rx::AddressRange range) {
auto pointer = std::bit_cast<void *>(range.beginAddress());
#ifdef _WIN32
auto reservation = VirtualAlloc2(nullptr, pointer, range.size(),
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, nullptr, 0);
if (reservation == nullptr) {
return std::errc::invalid_argument;
}
#else
#ifdef MAP_FIXED_NOREPLACE
static constexpr auto kMapFixedNoReplace = MAP_FIXED_NOREPLACE;
#else
static constexpr auto kMapFixedNoReplace = MAP_FIXED;
#ifdef ANDROID
#include "format-base.hpp"
#endif
auto reservation = ::mmap(pointer, range.size(), PROT_NONE,
MAP_ANON | kMapFixedNoReplace | MAP_PRIVATE, -1, 0);
if (reservation == MAP_FAILED) {
return std::errc{errno};
}
#endif
return {};
}
std::errc rx::releaseVirtualSpace(rx::AddressRange range,
[[maybe_unused]] std::size_t alignment) {
#ifdef _WIN32
// simple and stupid implementation
for (std::uintptr_t address = range.beginAddress();
address < range.endAddress(); address += alignment) {
auto pointer = std::bit_cast<void *>(address);
if (!UnmapViewOfFileEx(pointer, MEM_PRESERVE_PLACEHOLDER)) {
VirtualFree(pointer, alignment, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
}
}
#else
auto pointer = std::bit_cast<void *>(range.beginAddress());
auto reservation = ::mmap(pointer, range.size(), PROT_NONE,
MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
if (!reservation || reservation != pointer) {
return std::errc{errno};
}
#endif
return {};
}
std::pair<rx::Mappable, std::errc>
rx::Mappable::CreateMemory(std::size_t size) {
rx::Mappable result;
@ -80,12 +32,22 @@ rx::Mappable::CreateMemory(std::size_t size) {
}
result.m_handle = handle;
#else
#ifdef ANDROID
auto name = rx::format("/{}-{:x}", (void *)&result, size);
auto fd = ::shm_open(name.c_str(), O_CREAT | O_TRUNC, 0666);
#else
auto fd = ::memfd_create("", 0);
#endif
if (fd < 0) {
return {{}, std::errc{errno}};
}
#ifdef ANDROID
::shm_unlink(name.c_str());
#endif
result.m_handle = fd;
if (::ftruncate(fd, size) < 0) {
@ -119,7 +81,7 @@ std::pair<rx::Mappable, std::errc> rx::Mappable::CreateSwap(std::size_t size) {
}
std::errc rx::Mappable::map(rx::AddressRange virtualRange, std::size_t offset,
rx::EnumBitSet<Protection> protection,
rx::EnumBitSet<mem::Protection> protection,
[[maybe_unused]] std::size_t alignment) {
#ifdef _WIN32
static const DWORD protTable[] = {
@ -133,11 +95,11 @@ std::errc rx::Mappable::map(rx::AddressRange virtualRange, std::size_t offset,
PAGE_EXECUTE_READWRITE, // XRW
};
auto prot =
protTable[(protection & (Protection::R | Protection::W | Protection::X))
.toUnderlying()];
auto prot = protTable[(protection & (mem::Protection::R | mem::Protection::W |
mem::Protection::X))
.toUnderlying()];
releaseVirtualSpace(virtualRange, alignment);
mem::release(virtualRange, alignment);
for (std::uintptr_t address = virtualRange.beginAddress();
address < virtualRange.endAddress();
@ -152,18 +114,16 @@ std::errc rx::Mappable::map(rx::AddressRange virtualRange, std::size_t offset,
return std::errc::invalid_argument;
}
}
return {};
#else
int prot = 0;
if (protection & Protection::R) {
if (protection & mem::Protection::R) {
prot |= PROT_READ;
}
if (protection & Protection::W) {
if (protection & mem::Protection::W) {
prot |= PROT_READ | PROT_WRITE;
}
if (protection & Protection::X) {
if (protection & mem::Protection::X) {
prot |= PROT_EXEC;
}
@ -175,9 +135,9 @@ std::errc rx::Mappable::map(rx::AddressRange virtualRange, std::size_t offset,
if (result == MAP_FAILED) {
return std::errc{errno};
}
#endif
return {};
#endif
}
void rx::Mappable::destroy() {

View file

@ -1,50 +1,205 @@
#include "mem.hpp"
#include "print.hpp"
#ifdef __linux__
#include "die.hpp"
#ifdef _WIN32
#define NTDDI_VERSION NTDDI_WIN10_NI
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <cstdio>
#include <sys/mman.h>
#include <unistd.h>
extern const std::size_t rx::mem::pageSize = sysconf(_SC_PAGE_SIZE);
void *rx::mem::map(void *address, std::size_t size, int prot, int flags, int fd,
std::ptrdiff_t offset) {
return ::mmap(address, size, prot, flags, fd, offset);
}
void *rx::mem::reserve(std::size_t size) {
return map(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS);
}
bool rx::mem::reserve(void *address, std::size_t size) {
return map(address, size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED) != MAP_FAILED;
}
bool rx::mem::protect(void *address, std::size_t size, int prot) {
return ::mprotect(address, size, prot) == 0;
}
bool rx::mem::unmap(void *address, std::size_t size) {
return ::munmap(address, size) == 0;
}
void rx::mem::printStats() {
FILE *maps = fopen("/proc/self/maps", "r");
if (!maps) {
return;
}
char *line = nullptr;
std::size_t size = 0;
while (getline(&line, &size, maps) > 0) {
rx::print("{}", line);
}
free(line);
fclose(maps);
}
#endif
#ifdef _WIN32
const std::size_t rx::mem::pageSize = [] {
SYSTEM_INFO info;
::GetSystemInfo(&info);
return info.dwPageSize;
}();
#else
const std::size_t rx::mem::pageSize = sysconf(_SC_PAGE_SIZE);
#endif
std::errc rx::mem::reserve(rx::AddressRange range) {
auto pointer = std::bit_cast<void *>(range.beginAddress());
#ifdef _WIN32
auto reservation = VirtualAlloc2(nullptr, pointer, range.size(),
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS, nullptr, 0);
if (reservation == nullptr) {
return std::errc::invalid_argument;
}
#else
#ifdef MAP_FIXED_NOREPLACE
static constexpr auto kMapFixedNoReplace = MAP_FIXED_NOREPLACE;
#else
static constexpr auto kMapFixedNoReplace = MAP_FIXED;
#endif
auto reservation = ::mmap(pointer, range.size(), PROT_NONE,
MAP_ANON | kMapFixedNoReplace | MAP_PRIVATE, -1, 0);
if (reservation == MAP_FAILED) {
return std::errc{errno};
}
#endif
return {};
}
std::errc rx::mem::release(rx::AddressRange range,
[[maybe_unused]] std::size_t alignment) {
#ifdef _WIN32
// simple and stupid implementation
for (std::uintptr_t address = range.beginAddress();
address < range.endAddress(); address += alignment) {
auto pointer = std::bit_cast<void *>(address);
if (!UnmapViewOfFileEx(pointer, MEM_PRESERVE_PLACEHOLDER)) {
VirtualFree(pointer, alignment, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
}
}
#else
auto pointer = std::bit_cast<void *>(range.beginAddress());
auto reservation = ::mmap(pointer, range.size(), PROT_NONE,
MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
if (!reservation || reservation != pointer) {
return std::errc{errno};
}
#endif
return {};
}
std::errc rx::mem::protect(rx::AddressRange range,
rx::EnumBitSet<Protection> prot) {
auto pointer = std::bit_cast<void *>(range.beginAddress());
#ifdef _WIN32
static const DWORD protTable[] = {
PAGE_NOACCESS, // 0
PAGE_READONLY, // R
PAGE_EXECUTE_READWRITE, // W
PAGE_EXECUTE_READWRITE, // RW
PAGE_EXECUTE, // X
PAGE_EXECUTE_READWRITE, // XR
PAGE_EXECUTE_READWRITE, // XW
PAGE_EXECUTE_READWRITE, // XRW
};
auto rawProt =
(prot & (mem::Protection::R | mem::Protection::W | mem::Protection::X))
.toUnderlying();
auto wProt = protTable[rawProt];
if (!VirtualProtect(pointer, range.size(), wProt, nullptr)) {
return std::errc::invalid_argument;
}
#else
if (::mprotect(pointer, range.size(), prot.toUnderlying())) {
return std::errc{errno};
}
#endif
return {};
}
std::vector<rx::mem::VirtualQueryEntry> rx::mem::query(rx::AddressRange range) {
std::vector<VirtualQueryEntry> result;
#ifdef _WIN32
std::uintptr_t address = range.beginAddress();
while (address < range.endAddress()) {
MEMORY_BASIC_INFORMATION info;
if (!VirtualQuery((void *)address, &info, sizeof(info))) {
rx::die("VirtualQuery: failed, address = {:x}", address);
}
auto region = rx::AddressRange::fromBeginSize(
(std::uintptr_t)info.BaseAddress, info.RegionSize)
.intersection(range);
address = region.endAddress();
if (info.State == MEM_FREE || !region.isValid() ||
!range.contains(region)) {
continue;
}
rx::EnumBitSet<Protection> flags = {};
switch (info.AllocationProtect & 0xff) {
case PAGE_NOACCESS:
break;
case PAGE_READONLY:
flags = Protection::R;
break;
case PAGE_READWRITE:
case PAGE_WRITECOPY:
flags = Protection::R | Protection::W;
break;
case PAGE_EXECUTE:
flags = Protection::X;
break;
case PAGE_EXECUTE_READ:
flags = Protection::X | Protection::R;
break;
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY:
flags = Protection::X | Protection::W | Protection::R;
break;
}
result.emplace_back(region, flags);
}
#elif defined(__linux)
char buf[1024];
auto maps = std::fopen("/proc/self/maps", "r");
while (std::fgets(buf, sizeof(buf), maps)) {
std::uint64_t beginAddress;
std::uint64_t endAddress;
char flagChars[5];
std::sscanf(buf, "%lx-%lx %4s", &beginAddress, &endAddress, flagChars);
auto region = rx::AddressRange::fromBeginEnd(beginAddress, endAddress)
.intersection(range);
if (!region.isValid()) {
continue;
}
if (region.beginAddress() >= range.beginAddress()) {
break;
}
rx::EnumBitSet<Protection> flags = {};
if (flagChars[0] == 'r') {
flags |= Protection::R;
}
if (flagChars[1] == 'w') {
flags |= Protection::W;
}
if (flagChars[2] == 'x') {
flags |= Protection::X;
}
result.emplace_back(region, flags);
}
std::fclose(maps);
#elif defined(__APPLE__)
// FIXME: use mach_vm_region_info?
// workaround: assume all pages are not used
#else
#error "Not implemented"
#endif
return result;
}