From 815d274736515d7e803902db747f84734b73d11e Mon Sep 17 00:00:00 2001 From: alfalfa-star Date: Tue, 6 Oct 2026 20:43:07 +0200 Subject: [PATCH] feat(agc): import shared direct memory through udmabuf on Linux On Linux, direct memory is a memfd mapped MAP_SHARED. amdgpu accepts only private anonymous memory for a host pointer import (libdrm sets AMDGPU_GEM_USERPTR_ANONONLY), so every import of direct memory failed with VK_ERROR_INVALID_EXTERNAL_HANDLE and its ranges were copied. When a host pointer import fails, the range's memfd slice is turned into a dma-buf through /dev/udmabuf and imported with VK_EXT_external_memory_dma_buf. The direct memory backing is sealed against shrinking and growing, as udmabuf requires. libkernel reports the backing of a guest range through GuestArenaSharedBacking. Without /dev/udmabuf, or past its size limit, the range is copied as before. APS5_TRACE_HOST_IMPORT now prints the mappings behind a failed import on Linux too. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/VulkanDevice.cpp | 9 ++ .../Graphics/include/Context.hpp | 1 + .../Graphics/src/GuestBufferMemory.cpp | 90 ++++++++++++++++--- core/libs/prx/libc/include/GuestArena.hpp | 7 ++ core/libs/prx/libc/src/GuestArena.cpp | 18 ++++ .../libkernel/DirectMemory/DirectMemory.cpp | 42 ++++++++- core/libs/tests/GuestMemory.cpp | 44 +++++++++ 7 files changed, 199 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index fac8a10b7..8f7befa51 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -208,6 +208,7 @@ struct VulkanDevice::State { bool depthBiasClamp = false; bool occlusionQueryPrecise = false; VkDeviceSize hostImportAlignment = 0; + bool dmaBufImport = false; bool depthRangeUnrestricted = false; bool samplerAnisotropy = false; bool textureCompressionBC = false; @@ -832,6 +833,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u } #endif deviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); +#ifndef _WIN32 + state->dmaBufImport = hasExtension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) && hasExtension(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME); + if (state->dmaBufImport) { + deviceExtensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); + deviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME); + } +#endif } VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT listRestartFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT}; if (hasExtension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) { @@ -2445,6 +2453,7 @@ Graphics::Context VulkanDevice::buildContext() const { state->depthClamp, state->hostImportAlignment }; + context.dmaBufImport = state->dmaBufImport; context.recorder = state->recorder.get(); context.descriptorCache = state->descriptorCache.get(); context.samplerCache = state->samplerCache.get(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 7817d7b41..32aa6cde0 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -106,6 +106,7 @@ struct Context { bool depthClamp = false; // Nonzero when VK_EXT_external_memory_host is enabled: the required host pointer alignment. VkDeviceSize hostImportAlignment = 0; + bool dmaBufImport = false; RenderCache* renderCache = nullptr; DrawQueue* drawQueue = nullptr; GraphicsPipelineCache* graphicsPipelines = nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 1a1c8941e..501565e5e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -13,7 +13,11 @@ #ifdef _WIN32 #include #else +#include +#include +#include #include +#include #endif #include #include @@ -170,9 +174,7 @@ void retireImport(const Context& context, HostImports& state, std::map(entry.base); +const char* bindImport(const Context& context, HostImport& entry, VkExternalMemoryHandleTypeFlagBits handleType, const void* import, std::uint32_t importTypes, bool& allocated, VkResult& failure) { const auto failed = [&](const char* step, VkResult result) -> const char* { if (entry.buffer) context.Function("vkDestroyBuffer")(context.device, entry.buffer, nullptr); if (entry.memory) context.Function("vkFreeMemory")(context.device, entry.memory, nullptr); @@ -181,25 +183,24 @@ const char* createImport(const Context& context, HostImport& entry, VkResult& fa failure = result; return step; }; - const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT}; + allocated = false; + const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, static_cast(handleType)}; VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; - info.size = bytes; + info.size = entry.bytes; // INDIRECT_BUFFER: DISPATCH_INDIRECT group counts are read in place (VulkanDevice::DispatchIndirect). info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (const auto result = context.Function("vkCreateBuffer")(context.device, &info, nullptr, &entry.buffer); result != VK_SUCCESS) return failed("vkCreateBuffer", result); - VkMemoryHostPointerPropertiesEXT pointer{VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT}; - if (const auto result = context.Function("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host, &pointer); result != VK_SUCCESS) return failed("vkGetMemoryHostPointerPropertiesEXT", result); VkMemoryRequirements requirements{}; context.Function("vkGetBufferMemoryRequirements")(context.device, entry.buffer, &requirements); - const auto types = requirements.memoryTypeBits & pointer.memoryTypeBits; + const auto types = requirements.memoryTypeBits & importTypes; if (types == 0) return failed("memory type selection", VK_ERROR_FORMAT_NOT_SUPPORTED); - const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host}; - const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, &import, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0}; + const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, import, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0}; VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &flags}; - allocation.allocationSize = bytes; + allocation.allocationSize = entry.bytes; allocation.memoryTypeIndex = static_cast(std::countr_zero(types)); if (const auto result = context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &entry.memory); result != VK_SUCCESS) return failed("vkAllocateMemory", result); + allocated = true; if (const auto result = context.Function("vkBindBufferMemory")(context.device, entry.buffer, entry.memory, 0); result != VK_SUCCESS) return failed("vkBindBufferMemory", result); const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, entry.buffer}; entry.address = context.Function("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo); @@ -207,6 +208,61 @@ const char* createImport(const Context& context, HostImport& entry, VkResult& fa return nullptr; } +const char* createHostPointerImport(const Context& context, HostImport& entry, VkResult& failure) { + void* const host = entry.alias != nullptr ? entry.alias : reinterpret_cast(entry.base); + VkMemoryHostPointerPropertiesEXT pointer{VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT}; + if (const auto result = context.Function("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host, &pointer); result != VK_SUCCESS) { + failure = result; + return "vkGetMemoryHostPointerPropertiesEXT"; + } + const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host}; + bool allocated = false; + return bindImport(context, entry, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, &import, pointer.memoryTypeBits, allocated, failure); +} + +#ifndef _WIN32 +int udmabufDevice() { + static const int device = open("/dev/udmabuf", O_RDWR | O_CLOEXEC); + return device; +} + +const char* createDmaBufImport(const Context& context, HostImport& entry, int file, std::uint64_t offset, VkResult& failure) { + udmabuf_create request{}; + request.memfd = static_cast(file); + request.flags = UDMABUF_FLAGS_CLOEXEC; + request.offset = offset; + request.size = entry.bytes; + const int device = udmabufDevice(); + const int buffer = device < 0 ? -1 : ioctl(device, UDMABUF_CREATE, &request); + close(file); + if (buffer < 0) { + failure = VK_ERROR_INVALID_EXTERNAL_HANDLE; + return device < 0 ? "open /dev/udmabuf" : "UDMABUF_CREATE"; + } + VkMemoryFdPropertiesKHR properties{VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR}; + if (const auto result = context.Function("vkGetMemoryFdPropertiesKHR")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, buffer, &properties); result != VK_SUCCESS) { + close(buffer); + failure = result; + return "vkGetMemoryFdPropertiesKHR"; + } + const VkImportMemoryFdInfoKHR import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, buffer}; + bool allocated = false; + const char* step = bindImport(context, entry, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, &import, properties.memoryTypeBits, allocated, failure); + if (!allocated) close(buffer); + return step; +} +#endif + +const char* createImport(const Context& context, HostImport& entry, VkResult& failure) { + const char* step = createHostPointerImport(context, entry, failure); +#ifndef _WIN32 + int file = -1; + std::uint64_t offset = 0; + if (step != nullptr && context.dmaBufImport && GuestArena::GuestArenaSharedBacking_nid_postfix(static_cast(entry.base), static_cast(entry.bytes), &file, &offset)) step = createDmaBufImport(context, entry, file, offset, failure); +#endif + return step; +} + #ifndef _WIN32 enum class ImportWatchRequest : std::uint8_t { Probe, Watch, Unwatch }; @@ -337,6 +393,18 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s std::fprintf(stderr, "[gpu] 0x%llx+0x%llx state 0x%lx protect 0x%lx type 0x%lx allocation 0x%llx\n", static_cast(cursor), static_cast(std::min(regionEnd, base + bytes) - cursor), info.State, info.Protect, info.Type, reinterpret_cast(info.AllocationBase)); cursor = regionEnd; } +#else + static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr; + if (std::FILE* maps = trace ? std::fopen("/proc/self/maps", "r") : nullptr) { + char line[512]; + while (std::fgets(line, sizeof(line), maps) != nullptr) { + unsigned long long first = 0; + unsigned long long last = 0; + if (std::sscanf(line, "%llx-%llx", &first, &last) != 2 || last <= base || first >= base + bytes) continue; + std::fprintf(stderr, "[gpu] %s", line); + } + std::fclose(maps); + } #endif return nullptr; } diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index 1367e27e5..6a54c7980 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -8,6 +8,10 @@ // absolute address and breaks on host addresses outside that range. namespace GuestArena { +#ifndef _WIN32 +using SharedBackingResolver = bool (*)(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset); +#endif + extern "C" { bool GuestArenaAvailable_nid_postfix(); @@ -32,6 +36,9 @@ void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes); void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection); void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes); void GuestArenaUnmapAlias_nid_postfix(void* alias); +#else +void GuestArenaSetSharedBacking_nid_postfix(SharedBackingResolver resolver); +bool GuestArenaSharedBacking_nid_postfix(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset); #endif bool GuestArenaBeginHostWrite_nid_postfix(void* pointer, std::size_t bytes); void GuestArenaEndHostWrite_nid_postfix(void* pointer, std::size_t bytes); diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 528bf3542..ff7a0b4f0 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/WindowsMappings.hpp" #include +#include #include #include #include @@ -267,4 +268,21 @@ void GuestArenaEndHostWrite_nid_postfix(void* pointer, std::size_t bytes) { #endif } +#ifndef _WIN32 +namespace { + +std::atomic sharedBackingResolver{nullptr}; + +} + +void GuestArenaSetSharedBacking_nid_postfix(SharedBackingResolver resolver) { + sharedBackingResolver.store(resolver, std::memory_order_release); +} + +bool GuestArenaSharedBacking_nid_postfix(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset) { + const auto resolver = sharedBackingResolver.load(std::memory_order_acquire); + return resolver != nullptr && resolver(address, bytes, file, offset); +} +#endif + } diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 8fed7f059..297dd9d94 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -18,6 +18,7 @@ #include #if defined(__linux__) +#include #include #include #include @@ -233,18 +234,27 @@ public: throw std::system_error(error, std::system_category(), message); } #else - file = memfd_create("direct memory", MFD_CLOEXEC); + file = memfd_create("direct memory", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (file < 0) throw std::system_error(errno, std::generic_category(), "create direct memory backing"); if (ftruncate(file, static_cast(bytes)) != 0) { const int error = errno; ::close(file); throw std::system_error(error, std::generic_category(), "size direct memory backing"); } + if (fcntl(file, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW) != 0) { + const int error = errno; + ::close(file); + throw std::system_error(error, std::generic_category(), "seal direct memory backing"); + } #endif } int MemoryType() const { return memoryType; } +#if defined(__linux__) + int File() const { return file; } +#endif + ~PhysicalBacking() { #ifdef _WIN32 CloseHandle(section); @@ -353,6 +363,32 @@ void WatchMapping(std::uintptr_t address, std::size_t len) { } #endif +#if defined(__linux__) +bool SharedBacking(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset) { + std::lock_guard lock(g_directLock); + const auto next = g_directMappings.upper_bound(address); + if (bytes == 0 || bytes > std::numeric_limits::max() - address || next == g_directMappings.begin()) return false; + auto it = std::prev(next); + if (address >= it->second.end) return false; + const auto backing = it->second.backing; + const auto phys = it->second.phys + (address - it->first); + const auto page = g_physPages.find(phys - phys % PS5_PAGE_SIZE); + if (page == g_physPages.end() || page->second.backing != backing) return false; + const auto end = address + bytes; + for (auto covered = it->second.end; covered < end;) { + const auto following = std::next(it); + if (following == g_directMappings.end() || following->first != covered || following->second.backing != backing || following->second.phys != it->second.phys + (it->second.end - it->first)) return false; + it = following; + covered = it->second.end; + } + const int duplicate = fcntl(backing->File(), F_DUPFD_CLOEXEC, 0); + if (duplicate < 0) throw std::system_error(errno, std::generic_category(), "duplicate direct memory backing"); + *file = duplicate; + *offset = page->second.offset + phys % PS5_PAGE_SIZE; + return true; +} +#endif + void AddMapping(std::uintptr_t address, std::size_t len, std::uint64_t phys, int nativeProt) { ValidatePhysicalRange(phys, len); EraseMappings(address, address + len); @@ -754,6 +790,10 @@ void CreateDirectMemoryBacking(int64_t start, size_t len, int memoryType) { for (std::size_t offset = 0; offset < len; offset += PS5_PAGE_SIZE) { if (g_physPages.contains(first + offset)) throw std::runtime_error("physical allocation overlaps live direct memory"); } +#if defined(__linux__) + static std::once_flag registered; + std::call_once(registered, [] { GuestArena::GuestArenaSetSharedBacking_nid_postfix(&SharedBacking); }); +#endif const auto backing = std::make_shared(len, memoryType); std::map pages; for (std::size_t offset = 0; offset < len; offset += PS5_PAGE_SIZE) pages.emplace(first + offset, PhysicalPage{backing, offset}); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 92d578560..17c7fc45c 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -22,6 +22,7 @@ #include #include #if defined(__linux__) +#include #include #include #include @@ -936,6 +937,48 @@ static void CheckDirectMemoryBackingNeedsNoFilesystem() { Require(sceKernelReleaseDirectMemory(phys, page) == 0); } +static void CheckDirectMemorySharedBacking() { + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); + void* first = nullptr; + Require(sceKernelMapDirectMemory(&first, page * 2, 3, 0, phys + page, 0) == 0); + const auto address = reinterpret_cast(first); + static_cast(first)[page + 5] = 0x5a; + int file = -1; + std::uint64_t offset = 0; + Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address + page, page, &file, &offset)); + Require(offset == page * 2); + unsigned char byte = 0; + Require(pread(file, &byte, 1, static_cast(offset + 5)) == 1 && byte == 0x5a); + const int seals = fcntl(file, F_GET_SEALS); + Require(seals >= 0 && (seals & F_SEAL_SHRINK) != 0 && (seals & F_SEAL_GROW) != 0); + Require((fcntl(file, F_GETFD) & FD_CLOEXEC) != 0); + close(file); + Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 2, &file, &offset) && offset == page); + close(file); + Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 3, &file, &offset)); + Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, 0, &file, &offset)); + void* flexible = nullptr; + Require(sceKernelMapFlexibleMemory(&flexible, page * 2, 3, 0) == 0); + const auto flexibleAddress = reinterpret_cast(flexible); + Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page, &file, &offset)); + void* low = flexible; + Require(sceKernelMapDirectMemory(&low, page, 3, 0x10, phys, 0) == 0); + void* high = static_cast(flexible) + page; + Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page, 0) == 0); + Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset) && offset == 0); + close(file); + Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page * 2, 0) == 0); + Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset)); + Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset) && offset == page * 2); + close(file); + Require(sceKernelMunmap(flexible, page * 2) == 0); + Require(sceKernelMunmap(first, page * 2) == 0); + Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page, &file, &offset)); + Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); +} + static void CheckDirectMemoryWriteWatch() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { std::puts("write watch unavailable: direct memory not tested"); @@ -1016,6 +1059,7 @@ int main() { CheckWriteWatch(); CheckDirectMemoryWriteWatch(); CheckDirectMemoryBackingNeedsNoFilesystem(); + CheckDirectMemorySharedBacking(); #endif constexpr std::size_t page = 0x4000; const auto failed = reinterpret_cast(static_cast(-1));