From 85b09bac279adc3db0e5060c728b3dfd156772df Mon Sep 17 00:00:00 2001 From: Jody Hagins Date: Wed, 27 Aug 2025 06:15:52 -0400 Subject: [PATCH 1/2] Use only get_virtual_memory_page_size() virtual_memory_page_size has been deprecated, but it was being used internally extensively. Prior to this change, static use was impossible if PAGESIZE or PAGE_SIZE weren't set. The deprecation has been taken one step forward in that virtual_memory_page_size is no longer available by default. This is a breaking change, but the previous behavior can be enabled with FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE. --- cmake/configuration.cmake | 2 + doc/options.md | 2 + include/foonathan/memory/virtual_memory.hpp | 8 ++- src/CMakeLists.txt | 5 ++ src/virtual_memory.cpp | 74 +++++++++++++-------- test/CMakeLists.txt | 3 +- test/virtual_memory.cpp | 28 ++++++++ 7 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 test/virtual_memory.cpp diff --git a/cmake/configuration.cmake b/cmake/configuration.cmake index 1145b44..70de629 100644 --- a/cmake/configuration.cmake +++ b/cmake/configuration.cmake @@ -64,3 +64,5 @@ option(FOONATHAN_MEMORY_EXTERN_TEMPLATE "whether or not common template instantiations are already provided by the library" ON) set(FOONATHAN_MEMORY_TEMPORARY_STACK_MODE 2 CACHE STRING "set to 0 to disable the per-thread stack completely, to 1 to disable the nitfy counter and to 2 to enable everything") + set(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE OFF CACHE BOOL + "enable to provide the deprecated variable foonathan::memory::virtual_memory_page_size") diff --git a/doc/options.md b/doc/options.md index 9fc1a1e..9cf6933 100644 --- a/doc/options.md +++ b/doc/options.md @@ -25,6 +25,8 @@ Mode `2` has a slight runtime overhead. * `FOONATHAN_MEMORY_DEBUG_*`: Specifies debugging options such as pointer check in `deallocate()` or filling newly allocated memory with values. They are set automatically for certain build types and cannot be overriden: All of them are enabled in `Debug` builds, the faster ones in `RelWithDebInfo` and none in `Release`. See [debugging](md_doc_debug_error.html#debugging) for a detailed description. +* `FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE`: The variable `virtual_memory_page_size` was deprecated in 2022, with release 0.7-2. It will henceforth be removed, unless `FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE` is defined. + A list of all options with description is generated by calling `cmake -LH`. ## Variables and targets diff --git a/include/foonathan/memory/virtual_memory.hpp b/include/foonathan/memory/virtual_memory.hpp index a5d1662..972675d 100644 --- a/include/foonathan/memory/virtual_memory.hpp +++ b/include/foonathan/memory/virtual_memory.hpp @@ -33,12 +33,14 @@ namespace foonathan virtual_memory_allocator_leak_checker) } // namespace detail +#if defined(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE) /// The page size of the virtual memory. /// All virtual memory allocations must be multiple of this size. /// It is usually 4KiB. /// \ingroup allocator /// \deprecated use \ref get_virtual_memory_page_size instead. extern const std::size_t virtual_memory_page_size; +#endif /// \returns the page size of the virtual memory. /// All virtual memory allocations must be multiple of this size. @@ -48,7 +50,7 @@ namespace foonathan /// Reserves virtual memory. /// \effects Reserves the given number of pages. - /// Each page is \ref virtual_memory_page_size big. + /// Each page is \ref get_virtual_memory_page_size big. /// \returns The address of the first reserved page, /// or \c nullptr in case of error. /// \note The memory may not be used, it must first be commited. @@ -111,7 +113,7 @@ namespace foonathan /// \returns The maximum node size by returning the maximum value. std::size_t max_node_size() const noexcept; - /// \returns The maximum alignment which is the same as the \ref virtual_memory_page_size. + /// \returns The maximum alignment which is the same as the \ref get_virtual_memory_page_size. std::size_t max_alignment() const noexcept; }; @@ -131,7 +133,7 @@ namespace foonathan public: /// \effects Creates it giving it the block size and the total number of blocks it can allocate. /// It reserves enough virtual memory for block_size * no_blocks. - /// \requires \c block_size must be non-zero and a multiple of the \ref virtual_memory_page_size. + /// \requires \c block_size must be non-zero and a multiple of the \ref get_virtual_memory_page_size. /// \c no_blocks must be bigger than \c 1. /// \throws \ref out_of_memory if it cannot reserve the virtual memory. explicit virtual_block_allocator(std::size_t block_size, std::size_t no_blocks); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e736ae5..c54aacb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,6 +90,11 @@ target_compile_definitions(foonathan_memory PUBLIC FOONATHAN_MEMORY_VERSION_MAJOR=$CACHE{FOONATHAN_MEMORY_VERSION_MAJOR} FOONATHAN_MEMORY_VERSION_MINOR=$CACHE{FOONATHAN_MEMORY_VERSION_MINOR} FOONATHAN_MEMORY_VERSION_PATCH=$CACHE{FOONATHAN_MEMORY_VERSION_PATCH}) +if(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE) + target_compile_definitions(foonathan_memory PUBLIC + FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE=1) +endif() + if(NOT MSVC) target_compile_features(foonathan_memory PUBLIC cxx_constexpr) endif() diff --git a/src/virtual_memory.cpp b/src/virtual_memory.cpp index 7c8ca00..0aba09d 100644 --- a/src/virtual_memory.cpp +++ b/src/virtual_memory.cpp @@ -31,15 +31,25 @@ namespace } } // namespace -const std::size_t foonathan::memory::virtual_memory_page_size = get_page_size(); +std::size_t foonathan::memory::get_virtual_memory_page_size() noexcept +{ + static const std::size_t page_size = get_page_size(); + return page_size; +} + +#if defined(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE) +const std::size_t foonathan::memory::virtual_memory_page_size = + foonathan::memory::get_virtual_memory_page_size(); +#endif void* foonathan::memory::virtual_memory_reserve(std::size_t no_pages) noexcept { auto pages = #if (_MSC_VER <= 1900) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - VirtualAlloc(nullptr, no_pages * virtual_memory_page_size, MEM_RESERVE, PAGE_READWRITE); + VirtualAlloc(nullptr, no_pages * get_virtual_memory_page_size(), MEM_RESERVE, + PAGE_READWRITE); #else - VirtualAllocFromApp(nullptr, no_pages * virtual_memory_page_size, MEM_RESERVE, + VirtualAllocFromApp(nullptr, no_pages * get_virtual_memory_page_size(), MEM_RESERVE, PAGE_READWRITE); #endif return pages; @@ -56,9 +66,9 @@ void* foonathan::memory::virtual_memory_commit(void* memory, std::size_t no_page { auto region = #if (_MSC_VER <= 1900) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - VirtualAlloc(memory, no_pages * virtual_memory_page_size, MEM_COMMIT, PAGE_READWRITE); + VirtualAlloc(memory, no_pages * get_virtual_memory_page_size(), MEM_COMMIT, PAGE_READWRITE); #else - VirtualAllocFromApp(memory, no_pages * virtual_memory_page_size, MEM_COMMIT, + VirtualAllocFromApp(memory, no_pages * get_virtual_memory_page_size(), MEM_COMMIT, PAGE_READWRITE); #endif if (!region) @@ -69,7 +79,7 @@ void* foonathan::memory::virtual_memory_commit(void* memory, std::size_t no_page void foonathan::memory::virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept { - auto result = VirtualFree(memory, no_pages * virtual_memory_page_size, MEM_DECOMMIT); + auto result = VirtualFree(memory, no_pages * get_virtual_memory_page_size(), MEM_DECOMMIT); FOONATHAN_MEMORY_ASSERT_MSG(result, "cannot decommit memory"); (void)result; } @@ -78,13 +88,21 @@ void foonathan::memory::virtual_memory_decommit(void* memory, std::size_t no_pag #include #include +std::size_t foonathan::memory::get_virtual_memory_page_size() noexcept +{ #if defined(PAGESIZE) -const std::size_t foonathan::memory::virtual_memory_page_size = PAGESIZE; + return PAGESIZE; #elif defined(PAGE_SIZE) -const std::size_t foonathan::memory::virtual_memory_page_size = PAGE_SIZE; + return PAGE_SIZE; #else + static const std::size_t page_size = static_cast(sysconf(_SC_PAGESIZE)); + return page_size; +#endif +} + +#if defined(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE) const std::size_t foonathan::memory::virtual_memory_page_size = - static_cast(sysconf(_SC_PAGESIZE)); + foonathan::memory::get_virtual_memory_page_size(); #endif #ifndef MAP_ANONYMOUS @@ -93,21 +111,21 @@ const std::size_t foonathan::memory::virtual_memory_page_size = void* foonathan::memory::virtual_memory_reserve(std::size_t no_pages) noexcept { - auto pages = mmap(nullptr, no_pages * virtual_memory_page_size, PROT_NONE, + auto pages = mmap(nullptr, no_pages * get_virtual_memory_page_size(), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); return pages == MAP_FAILED ? nullptr : pages; } void foonathan::memory::virtual_memory_release(void* pages, std::size_t no_pages) noexcept { - auto result = munmap(pages, no_pages * virtual_memory_page_size); + auto result = munmap(pages, no_pages * get_virtual_memory_page_size()); FOONATHAN_MEMORY_ASSERT_MSG(result == 0, "cannot release pages"); (void)result; } void* foonathan::memory::virtual_memory_commit(void* memory, std::size_t no_pages) noexcept { - auto size = no_pages * virtual_memory_page_size; + auto size = no_pages * get_virtual_memory_page_size(); auto result = mprotect(memory, size, PROT_WRITE | PROT_READ); if (result != 0) return nullptr; @@ -124,7 +142,7 @@ void* foonathan::memory::virtual_memory_commit(void* memory, std::size_t no_page void foonathan::memory::virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept { - auto size = no_pages * virtual_memory_page_size; + auto size = no_pages * get_virtual_memory_page_size(); // advise that the memory won't be needed anymore #if defined(MADV_FREE) madvise(memory, size, MADV_FREE); @@ -142,17 +160,13 @@ void foonathan::memory::virtual_memory_decommit(void* memory, std::size_t no_pag #warning "virtual memory functions not available on your platform, define your own" #endif -std::size_t foonathan::memory::get_virtual_memory_page_size() noexcept -{ - return virtual_memory_page_size; -} - namespace { std::size_t calc_no_pages(std::size_t size) noexcept { - auto div = size / virtual_memory_page_size; - auto rest = size % virtual_memory_page_size; + auto const page_size = get_virtual_memory_page_size(); + auto div = size / page_size; + auto rest = size % page_size; return div + (rest != 0u) + (detail::debug_fence_size ? 2u : 1u); } @@ -165,15 +179,15 @@ void* virtual_memory_allocator::allocate_node(std::size_t size, std::size_t) if (!pages || !virtual_memory_commit(pages, no_pages)) FOONATHAN_THROW( out_of_memory({FOONATHAN_MEMORY_LOG_PREFIX "::virtual_memory_allocator", nullptr}, - no_pages * virtual_memory_page_size)); + no_pages * get_virtual_memory_page_size())); on_allocate(size); - return detail::debug_fill_new(pages, size, virtual_memory_page_size); + return detail::debug_fill_new(pages, size, get_virtual_memory_page_size()); } void virtual_memory_allocator::deallocate_node(void* node, std::size_t size, std::size_t) noexcept { - auto pages = detail::debug_fill_free(node, size, virtual_memory_page_size); + auto pages = detail::debug_fill_free(node, size, get_virtual_memory_page_size()); on_deallocate(size); @@ -189,7 +203,7 @@ std::size_t virtual_memory_allocator::max_node_size() const noexcept std::size_t virtual_memory_allocator::max_alignment() const noexcept { - return virtual_memory_page_size; + return get_virtual_memory_page_size(); } #if FOONATHAN_MEMORY_EXTERN_TEMPLATE @@ -199,10 +213,11 @@ template class foonathan::memory::allocator_traits; virtual_block_allocator::virtual_block_allocator(std::size_t block_size, std::size_t no_blocks) : block_size_(block_size) { - FOONATHAN_MEMORY_ASSERT(block_size % virtual_memory_page_size == 0u); + auto const page_size = get_virtual_memory_page_size(); + FOONATHAN_MEMORY_ASSERT(block_size % page_size == 0u); FOONATHAN_MEMORY_ASSERT(no_blocks > 0); auto total_size = block_size_ * no_blocks; - auto no_pages = total_size / virtual_memory_page_size; + auto no_pages = total_size / page_size; cur_ = static_cast(virtual_memory_reserve(no_pages)); if (!cur_) @@ -212,14 +227,15 @@ virtual_block_allocator::virtual_block_allocator(std::size_t block_size, std::si virtual_block_allocator::~virtual_block_allocator() noexcept { - virtual_memory_release(cur_, static_cast(end_ - cur_) / virtual_memory_page_size); + virtual_memory_release(cur_, + static_cast(end_ - cur_) / get_virtual_memory_page_size()); } memory_block virtual_block_allocator::allocate_block() { if (std::size_t(end_ - cur_) < block_size_) FOONATHAN_THROW(out_of_fixed_memory(info(), block_size_)); - auto mem = virtual_memory_commit(cur_, block_size_ / virtual_memory_page_size); + auto mem = virtual_memory_commit(cur_, block_size_ / get_virtual_memory_page_size()); if (!mem) FOONATHAN_THROW(out_of_fixed_memory(info(), block_size_)); cur_ += block_size_; @@ -232,7 +248,7 @@ void virtual_block_allocator::deallocate_block(memory_block block) noexcept { return static_cast(block.memory) == cur_ - block_size_; }, info(), block.memory); cur_ -= block_size_; - virtual_memory_decommit(cur_, block_size_ / virtual_memory_page_size); + virtual_memory_decommit(cur_, block_size_ / get_virtual_memory_page_size()); } allocator_info virtual_block_allocator::info() noexcept diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 787dc61..808dbe4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,7 +35,8 @@ set(tests memory_resource_adapter.cpp memory_stack.cpp segregator.cpp - smart_ptr.cpp) + smart_ptr.cpp + virtual_memory.cpp) add_executable(foonathan_memory_test ${tests}) target_link_libraries(foonathan_memory_test PRIVATE foonathan_memory doctest::doctest) diff --git a/test/virtual_memory.cpp b/test/virtual_memory.cpp new file mode 100644 index 0000000..3df3bd8 --- /dev/null +++ b/test/virtual_memory.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2025 Jonathan Müller and foonathan/memory contributors +// SPDX-License-Identifier: Zlib + +// tests all possible default allocator classes + +#include "virtual_memory.hpp" + +#include + +namespace +{ + + using namespace foonathan::memory; + + TEST_CASE("can use virtual_memory during static initialization") + { + static void* pages = virtual_memory_reserve(10); + REQUIRE(pages != nullptr); + + virtual_memory_release(pages, 10); + pages = nullptr; + +#if defined(FOONATHAN_MEMORY_ALLOW_VIRTUAL_MEMORY_PAGE_SIZE) + CHECK(virtual_memory_page_size == get_virtual_memory_page_size()); +#endif + } + +} // namespace From f1569597e4a1d996dfd7357361c47cf59d0e45aa Mon Sep 17 00:00:00 2001 From: Jody Hagins Date: Wed, 27 Aug 2025 09:36:48 -0400 Subject: [PATCH 2/2] Little better test The initialization order is undetermined, and is difficult to reproduce in a test, but this one has a better chance of hitting the failure situation where the virtual_memory_page_size variable is accessed before it has been set, yielding a value of zero. This is basically the crash scenario that I encountered, which prompted these changes. --- test/virtual_memory.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/virtual_memory.cpp b/test/virtual_memory.cpp index 3df3bd8..c0d4d76 100644 --- a/test/virtual_memory.cpp +++ b/test/virtual_memory.cpp @@ -12,9 +12,10 @@ namespace using namespace foonathan::memory; + static void* pages = virtual_memory_reserve(10); + TEST_CASE("can use virtual_memory during static initialization") { - static void* pages = virtual_memory_reserve(10); REQUIRE(pages != nullptr); virtual_memory_release(pages, 10);