diff --git a/rx/include/rx/Mappable.hpp b/rx/include/rx/Mappable.hpp new file mode 100644 index 000000000..409357d7d --- /dev/null +++ b/rx/include/rx/Mappable.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "AddressRange.hpp" +#include "EnumBitSet.hpp" +#include +#include + +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 *; + static constexpr NativeHandle kInvalidHandle = nullptr; +#else + using NativeHandle = int; + static constexpr auto kInvalidHandle = NativeHandle(-1); +#endif + + NativeHandle m_handle = kInvalidHandle; + +public: + Mappable() = default; + Mappable(Mappable &&other) noexcept { *this = std::move(other); } + Mappable(const Mappable &) = delete; + Mappable &operator=(Mappable &&other) noexcept { + std::swap(m_handle, other.m_handle); + return *this; + } + Mappable &operator=(const Mappable &) = delete; + ~Mappable() noexcept { + if (m_handle != kInvalidHandle) { + destroy(); + } + } + + static std::pair CreateMemory(std::size_t size); + static std::pair CreateSwap(std::size_t size); + std::errc map(rx::AddressRange virtualRange, std::size_t offset, + rx::EnumBitSet protection, std::size_t alignment); + + [[nodiscard]] NativeHandle release() { + return std::exchange(m_handle, kInvalidHandle); + } + [[nodiscard]] NativeHandle native_handle() const { return m_handle; } + explicit operator bool() const { return m_handle != kInvalidHandle; } + +private: + void destroy(); +}; +} // namespace rx diff --git a/rx/src/Mappable.cpp b/rx/src/Mappable.cpp new file mode 100644 index 000000000..7b4d74cb2 --- /dev/null +++ b/rx/src/Mappable.cpp @@ -0,0 +1,191 @@ +#include "Mappable.hpp" +#include + +#ifndef _WIN32 +#include +#include +#include +#include +#else +#include +#define NTDDI_VERSION NTDDI_WIN10_NI +#define WIN32_LEAN_AND_MEAN +#include +#endif + +std::errc rx::reserveVirtualSpace(rx::AddressRange range) { + auto pointer = std::bit_cast(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::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(address); + if (!UnmapViewOfFileEx(pointer, MEM_PRESERVE_PLACEHOLDER)) { + VirtualFree(pointer, alignment, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); + } + } +#else + auto pointer = std::bit_cast(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::CreateMemory(std::size_t size) { + rx::Mappable result; + +#ifdef _WIN32 + auto handle = CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, + FILE_MAP_ALL_ACCESS, PAGE_EXECUTE_READWRITE, + SEC_COMMIT, size, nullptr, nullptr, 0); + + if (!handle) { + return {rx::Mappable{}, std::errc::invalid_argument}; + } + + result.m_handle = handle; +#else + auto fd = ::memfd_create("", 0); + if (fd < 0) { + return {{}, std::errc{errno}}; + } + + result.m_handle = fd; + + if (::ftruncate(fd, size) < 0) { + return {{}, std::errc{errno}}; + } +#endif + + return {std::move(result), std::errc{}}; +} + +std::pair rx::Mappable::CreateSwap(std::size_t size) { +#ifdef _WIN32 + return CreateMemory(size); +#else + char temp_filename[] = "./.rx-swap-XXXXXXXXXXX"; + int fd = ::mkstemp(temp_filename); + if (fd < 0) { + return {{}, std::errc{errno}}; + } + ::unlink(temp_filename); + + rx::Mappable result; + result.m_handle = fd; + + if (::ftruncate(fd, size) < 0) { + return {{}, std::errc{errno}}; + } + + return {std::move(result), {}}; +#endif +} + +std::errc rx::Mappable::map(rx::AddressRange virtualRange, std::size_t offset, + rx::EnumBitSet protection, + [[maybe_unused]] std::size_t alignment) { +#ifdef _WIN32 + DWORD prot = 0; + if (!protection) { + prot = PAGE_NOACCESS; + } else if (protection == Protection::R) { + prot = PAGE_READONLY; + } else if (protection & Protection::X) { + if (protection & Protection::W) { + prot = PAGE_EXECUTE_READWRITE; + } else if (protection & Protection::R) { + prot = PAGE_EXECUTE_READ; + } else { + prot = PAGE_EXECUTE; + } + } else { + prot = PAGE_READWRITE; + } + + releaseVirtualSpace(virtualRange, alignment); + + for (std::uintptr_t address = virtualRange.beginAddress(); + address < virtualRange.endAddress(); + address += alignment, offset += alignment) { + auto pointer = std::bit_cast(address); + + auto result = + MapViewOfFile3((HANDLE)m_handle, nullptr, pointer, offset, alignment, + MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); + + if (!result) { + return std::errc::invalid_argument; + } + } + + return {}; +#else + int prot = 0; + + if (protection & Protection::R) { + prot |= PROT_READ; + } + if (protection & Protection::W) { + prot |= PROT_READ | PROT_WRITE; + } + if (protection & Protection::X) { + prot |= PROT_EXEC; + } + + auto address = std::bit_cast(virtualRange.beginAddress()); + + auto result = ::mmap(address, virtualRange.size(), prot, + MAP_SHARED | MAP_FIXED, m_handle, offset); + + if (result == MAP_FAILED) { + return std::errc{errno}; + } + + return {}; +#endif +} + +void rx::Mappable::destroy() { +#ifdef _WIN32 + CloseHandle((HANDLE)m_handle); +#else + ::close(m_handle); +#endif +}