From 9e632047de9929ea4d6db51016fd9b9baf59988a Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Thu, 9 Jan 2025 17:57:52 +0000 Subject: [PATCH] Add Windows Support for Lazy Commit of page map (#727) The Pagemap can potentially be accessed where there is no direct knowledge of the location. On platforms with lazy commit this can be handled by the OS faulting in a zero page. Here we modify the Windows PAL to add an exception handler that will commit pages in the read only range, i.e. the page map. We still require explicit notify_using to be able to modify the pagemap. --- src/snmalloc/pal/pal_windows.h | 86 +++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/src/snmalloc/pal/pal_windows.h b/src/snmalloc/pal/pal_windows.h index 1cbcd41..6a8fc38 100644 --- a/src/snmalloc/pal/pal_windows.h +++ b/src/snmalloc/pal/pal_windows.h @@ -1,6 +1,7 @@ #pragma once #include "../aal/aal.h" +#include "../ds_aal/ds_aal.h" #include "pal_timer_default.h" #ifdef _WIN32 @@ -48,13 +49,76 @@ namespace snmalloc low_memory_callbacks.notify_all(); } + // A list of reserved ranges, used to handle lazy commit on readonly pages. + // We currently only need one, so haven't implemented a backup if the + // initial 16 is insufficient. + inline static std::array, 16> reserved_ranges; + + // Lock for the reserved ranges. + inline static FlagWord reserved_ranges_lock{}; + + // Exception handler for handling lazy commit on readonly pages. + static LONG NTAPI + HandleReadonlyLazyCommit(struct _EXCEPTION_POINTERS* ExceptionInfo) + { + // Check this is an AV + if ( + ExceptionInfo->ExceptionRecord->ExceptionCode != + EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_CONTINUE_SEARCH; + + // Check this is a read access + if (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] != 0) + return EXCEPTION_CONTINUE_SEARCH; + + // Get faulting address from exception info. + snmalloc::address_t faulting_address = + ExceptionInfo->ExceptionRecord->ExceptionInformation[1]; + + bool found = false; + { + FlagLock lock(reserved_ranges_lock); + // Check if the address is in a reserved range. + for (auto& r : reserved_ranges) + { + if ((faulting_address - r.first) < r.second) + { + found = true; + break; + } + } + } + + if (!found) + return EXCEPTION_CONTINUE_SEARCH; + + // Commit the page as readonly + auto pagebase = snmalloc::bits::align_down(faulting_address, page_size); + VirtualAlloc((void*)pagebase, page_size, MEM_COMMIT, PAGE_READONLY); + + // Resume execution + return EXCEPTION_CONTINUE_EXECUTION; + } + + static void initialise_for_singleton(size_t*) noexcept + { + AddVectoredExceptionHandler(1, HandleReadonlyLazyCommit); + } + + // Ensure the exception handler is registered. + static void initialise_readonly_av() noexcept + { + static Singleton init; + init.get(); + } + public: /** * Bitmap of PalFeatures flags indicating the optional features that this * PAL supports. This PAL supports low-memory notifications. */ static constexpr uint64_t pal_features = LowMemoryNotification | Entropy | - Time + Time | LazyCommit # if defined(PLATFORM_HAS_VIRTUALALLOC2) && !defined(USE_SYSTEMATIC_TESTING) | AlignedAllocation # endif @@ -155,6 +219,26 @@ namespace snmalloc "out of memory: {} ({}) could not be committed", p, size); } + static void notify_using_readonly(void* p, size_t size) noexcept + { + initialise_readonly_av(); + + { + FlagLock lock(reserved_ranges_lock); + for (auto& r : reserved_ranges) + { + if (r.first == 0) + { + r.first = (address_t)p; + r.second = size; + return; + } + } + } + + error("Implementation error: Too many lazy commit regions!"); + } + /// OS specific function for zeroing memory template static void zero(void* p, size_t size) noexcept