// Native runtime - Memory utilities, allocation, and byte-swap operations // Part of the AC6 Recompilation native foundation #pragma once #include #include #include #include #include #include #include #include #include #include namespace rex { namespace memory { // For variable declarations (not return values or `this` pointer). // Not propagated. #define REX_RESTRICT_VAR __restrict // Aliasing-safe bit reinterpretation. template void Reinterpret(Dst& REX_RESTRICT_VAR dst, const Src& REX_RESTRICT_VAR src) { static_assert(sizeof(Dst) == sizeof(Src)); static_assert(std::is_trivially_copyable_v); static_assert(std::is_trivially_copyable_v); std::memcpy(&dst, &src, sizeof(Dst)); } template Dst Reinterpret(const Src& REX_RESTRICT_VAR src) { Dst dst; Reinterpret(dst, src); return dst; } // Returns the native page size of the system, in bytes. // This should be ~4KiB. size_t page_size(); // Returns the allocation granularity of the system, in bytes. // This is likely 64KiB. size_t allocation_granularity(); enum class PageAccess { kNoAccess = 0, kReadOnly = 1 << 0, kReadWrite = kReadOnly | 1 << 1, kExecuteReadOnly = kReadOnly | 1 << 2, kExecuteReadWrite = kReadWrite | 1 << 2, }; enum class AllocationType { kReserve = 1 << 0, kCommit = 1 << 1, kReserveCommit = kReserve | kCommit, }; enum class DeallocationType { kRelease = 1 << 0, kDecommit = 1 << 1, }; // Whether the host allows the pages to be allocated or mapped with // PageAccess::kExecuteReadWrite bool IsWritableExecutableMemorySupported(); // Whether PageAccess::kExecuteReadWrite is a supported and preferred way of // writing executable memory bool IsWritableExecutableMemoryPreferred(); // Allocates a block of memory at the given page-aligned base address. void* AllocFixed(void* base_address, size_t length, AllocationType allocation_type, PageAccess access); // Deallocates and/or releases the given block of memory. bool DeallocFixed(void* base_address, size_t length, DeallocationType deallocation_type); // Sets the access rights for the given block of memory bool Protect(void* base_address, size_t length, PageAccess access, PageAccess* out_old_access = nullptr); // Queries a region of pages to get the access rights. bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out); // Allocates a block of memory for a type with the given alignment. template inline T* AlignedAlloc(size_t alignment) { #if REX_PLATFORM_WIN32 return reinterpret_cast(_aligned_malloc(sizeof(T), alignment)); #else void* ptr = nullptr; if (posix_memalign(&ptr, alignment, sizeof(T))) { return nullptr; } return reinterpret_cast(ptr); #endif } // Frees memory previously allocated with AlignedAlloc. template void AlignedFree(T* ptr) { #if REX_PLATFORM_WIN32 _aligned_free(ptr); #else free(ptr); #endif } // Opaque file mapping handle. using FileMappingHandle = intptr_t; constexpr FileMappingHandle kFileMappingHandleInvalid = -1; FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, size_t length, PageAccess access, bool commit); void CloseFileMappingHandle(FileMappingHandle handle, const std::filesystem::path& path); void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, PageAccess access, size_t file_offset); bool UnmapFileView(FileMappingHandle handle, void* base_address, size_t length); inline size_t hash_combine(size_t seed) { return seed; } template size_t hash_combine(size_t seed, const T& v, const Ts&... vs) { std::hash hasher; seed ^= hasher(v) + 0x9E3779B9 + (seed << 6) + (seed >> 2); return hash_combine(seed, vs...); } inline void* low_address(void* address) { return reinterpret_cast(uint64_t(address) & 0xFFFFFFFF); } void copy_128_aligned(void* dest, const void* src, size_t count); void copy_and_swap_16_aligned(void* dest, const void* src, size_t count); void copy_and_swap_16_unaligned(void* dest, const void* src, size_t count); void copy_and_swap_32_aligned(void* dest, const void* src, size_t count); void copy_and_swap_32_unaligned(void* dest, const void* src, size_t count); void copy_and_swap_64_aligned(void* dest, const void* src, size_t count); void copy_and_swap_64_unaligned(void* dest, const void* src, size_t count); void copy_and_swap_16_in_32_aligned(void* dest, const void* src, size_t count); void copy_and_swap_16_in_32_unaligned(void* dest, const void* src, size_t count); template void copy_and_swap(T* dest, const T* src, size_t count) { bool is_aligned = reinterpret_cast(dest) % 32 == 0 && reinterpret_cast(src) % 32 == 0; if (sizeof(T) == 1) { std::memcpy(dest, src, count); } else if (sizeof(T) == 2) { auto ps = reinterpret_cast(src); auto pd = reinterpret_cast(dest); if (is_aligned) { copy_and_swap_16_aligned(pd, ps, count); } else { copy_and_swap_16_unaligned(pd, ps, count); } } else if (sizeof(T) == 4) { auto ps = reinterpret_cast(src); auto pd = reinterpret_cast(dest); if (is_aligned) { copy_and_swap_32_aligned(pd, ps, count); } else { copy_and_swap_32_unaligned(pd, ps, count); } } else if (sizeof(T) == 8) { auto ps = reinterpret_cast(src); auto pd = reinterpret_cast(dest); if (is_aligned) { copy_and_swap_64_aligned(pd, ps, count); } else { copy_and_swap_64_unaligned(pd, ps, count); } } else { assert_always("Invalid memory::copy_and_swap size"); } } /// Load a value of type T from arbitrary memory (handles unaligned access). template inline T load(const void* mem) { static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); T result; std::memcpy(&result, mem, sizeof(T)); return result; } /// Load a value of type T from memory and byte-swap it. template inline T load_and_swap(const void* mem) { return byte_swap(load(mem)); } // String specializations need custom logic template <> inline std::string load_and_swap(const void* mem) { std::string value; for (int i = 0;; ++i) { auto c = load_and_swap(reinterpret_cast(mem) + i); if (!c) { break; } value.push_back(static_cast(c)); } return value; } template <> inline std::u16string load_and_swap(const void* mem) { std::u16string value; for (int i = 0;; ++i) { auto c = load_and_swap(reinterpret_cast(mem) + i); if (!c) { break; } value.push_back(static_cast(c)); } return value; } /// Store a value of type T to arbitrary memory (handles unaligned access). template inline void store(void* mem, const T& value) { static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); std::memcpy(mem, &value, sizeof(T)); } /// Store a byte-swapped value of type T to memory. template inline void store_and_swap(void* mem, const T& value) { store(mem, byte_swap(value)); } // String specializations need custom logic template <> inline void store_and_swap(void* mem, const std::string_view& value) { for (size_t i = 0; i < value.size(); ++i) { store_and_swap(reinterpret_cast(mem) + i, value[i]); } } template <> inline void store_and_swap(void* mem, const std::string& value) { return store_and_swap(mem, value); } template <> inline void store_and_swap(void* mem, const std::u16string_view& value) { for (size_t i = 0; i < value.size(); ++i) { store_and_swap(reinterpret_cast(mem) + i, value[i]); } } template <> inline void store_and_swap(void* mem, const std::u16string& value) { return store_and_swap(mem, value); } using fourcc_t = uint32_t; // Get FourCC in host byte order constexpr inline fourcc_t make_fourcc(char a, char b, char c, char d) { return fourcc_t((static_cast(a) << 24) | (static_cast(b) << 16) | (static_cast(c) << 8) | static_cast(d)); } // Get FourCC in host byte order constexpr inline fourcc_t make_fourcc(const std::string_view fourcc) { if (fourcc.length() != 4) { throw std::runtime_error("Invalid fourcc length"); } return make_fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]); } } // namespace memory } // namespace rex