#ifndef EQEMU_KSM_HPP #define EQEMU_KSM_HPP #include "../eqemu_logsys.h" #include #include #include #ifdef _WIN32 #include // For _aligned_malloc, _aligned_free #include #else #include // For madvise #include // For sysconf, sbrk #endif // Page-aligned allocator for std::vector template class PageAlignedAllocator { public: using value_type = T; PageAlignedAllocator() noexcept = default; template PageAlignedAllocator(const PageAlignedAllocator&) noexcept {} T* allocate(std::size_t n) { void* ptr = nullptr; size_t size = n * sizeof(T); #ifdef _WIN32 // Simply allocate memory without alignment ptr = malloc(size); if (!ptr) throw std::bad_alloc(); #else size_t alignment = getPageSize(); // Get the system's page size if (posix_memalign(&ptr, alignment, size) != 0) { throw std::bad_alloc(); } #endif return static_cast(ptr); } void deallocate(T* p, std::size_t) noexcept { free(p); } private: size_t getPageSize() const { #ifdef _WIN32 SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); return sysInfo.dwPageSize; // Page size in bytes #else return static_cast(sysconf(_SC_PAGESIZE)); #endif }; }; template bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { return true; } template bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { return false; } // Kernel Samepage Merging (KSM) functionality namespace KSM { #ifdef _WIN32 // Windows-specific placeholder functions (no-op) inline void CheckPageAlignment(void* ptr) { } inline void* AllocatePageAligned(size_t size) { return memset(malloc(size), 0, size); } inline void MarkMemoryForKSM(void* start, size_t size) { } inline void AlignHeapToPageBoundary() { } inline void* MarkHeapStart() { return nullptr; } inline size_t MeasureHeapUsage(void* start) { return 0; } #else // Linux-specific functionality inline void CheckPageAlignment(void* ptr) { size_t page_size = sysconf(_SC_PAGESIZE); if (reinterpret_cast(ptr) % page_size == 0) { LogKSMDetail("Memory is page-aligned [{}]", ptr); } else { LogKSMDetail("Memory is NOT page-aligned [{}]", ptr); } } inline void* AllocatePageAligned(size_t size) { size_t page_size = sysconf(_SC_PAGESIZE); void* aligned_ptr = nullptr; if (posix_memalign(&aligned_ptr, page_size, size) != 0) { LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size); } std::memset(aligned_ptr, 0, size); return aligned_ptr; } inline void MarkMemoryForKSM(void* start, size_t size) { if (madvise(start, size, MADV_MERGEABLE) == 0) { LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size); } else { perror("madvise failed"); } } inline void AlignHeapToPageBoundary() { size_t page_size = sysconf(_SC_PAGESIZE); if (page_size == 0) { LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size); return; } void* current_break = sbrk(0); if (current_break == (void*)-1) { LogKSM("Failed to retrieve the current program break"); return; } uintptr_t current_address = reinterpret_cast(current_break); size_t misalignment = current_address % page_size; if (misalignment != 0) { size_t adjustment = page_size - misalignment; if (sbrk(adjustment) == (void*)-1) { LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment); return; } } LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0)); } inline void* MarkHeapStart() { void* current_pos = sbrk(0); AlignHeapToPageBoundary(); return current_pos; } inline size_t MeasureHeapUsage(void* start) { void* current_break = sbrk(0); return static_cast(current_break) - static_cast(start); } #endif inline size_t getPageSize() { #ifdef _WIN32 SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); return sysInfo.dwPageSize; // Page size in bytes #else return static_cast(sysconf(_SC_PAGESIZE)); // POSIX page size #endif }; template inline void PageAlignVectorAligned(std::vector>& vec) { if (vec.empty()) { return; } size_t page_size = getPageSize(); void* start = vec.data(); size_t size = vec.size() * sizeof(T); // Check if the memory is page-aligned if (reinterpret_cast(start) % page_size != 0) { // Allocate a new aligned vector std::vector> aligned_vec(vec.get_allocator()); aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy // Copy elements from the original vector aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end()); // Swap the aligned vector with the original vector vec.swap(aligned_vec); // Clear the temporary aligned vector to free its memory aligned_vec.clear(); // Verify the new alignment start = vec.data(); if (reinterpret_cast(start) % page_size != 0) { throw std::runtime_error("Failed to align vector memory to page boundaries."); } LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size); } else { LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size); } #ifndef _WIN32 // Mark memory for KSM (only on non-Windows systems) MarkMemoryForKSM(start, size); #endif } } #endif // EQEMU_KSM_HPP