From d8de8ec62568435de1d5b5dfd9e7d2b9c2edb958 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 18 Oct 2026 22:19:13 +0200 Subject: [PATCH 01/10] Add an optional scoped mimalloc allocator backend --- .github/workflows/build.yml | 85 +++++++++++++++++++++++++++ CMakeLists.txt | 33 ++++++++++- changelog.in | 8 +++ cmake/GecodeMimalloc.cmake | 46 +++++++++++++++ docs/cmake-build.md | 32 ++++++++++ gecode/support/allocator.cpp | 19 ++++++ gecode/support/allocator.hpp | 17 ++++++ test/package/allocator/CMakeLists.txt | 30 ++++++++++ test/package/allocator/boundary.cpp | 39 ++++++++++++ test/package/allocator/boundary.hh | 22 +++++++ test/package/allocator/main.cpp | 58 ++++++++++++++++++ 11 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 cmake/GecodeMimalloc.cmake create mode 100644 test/package/allocator/CMakeLists.txt create mode 100644 test/package/allocator/boundary.cpp create mode 100644 test/package/allocator/boundary.hh create mode 100644 test/package/allocator/main.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d70f7172f..4488055d53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -264,6 +264,91 @@ jobs: cmake --install "$GITHUB_WORKSPACE/build-ninja" --prefix "$GITHUB_WORKSPACE/install-ninja" bash .github/ci/flatzinc-tools-smoke.sh "$GITHUB_WORKSPACE/install-ninja" + build-cmake-allocator: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-15, windows-2022] + variant: [shared, static] + mimalloc: ["ON"] + include: + - os: ubuntu-latest + variant: shared + mimalloc: "OFF" + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + + - name: Build and install allocator package + shell: bash + run: | + set -euxo pipefail + shared=OFF + static=OFF + if [ "${{ matrix.variant }}" = shared ]; then shared=ON; else static=ON; fi + cmake -S . -B build-allocator \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DGECODE_BUILD_SHARED="$shared" -DGECODE_BUILD_STATIC="$static" \ + -DGECODE_ENABLE_MIMALLOC=${{ matrix.mimalloc }} \ + -DGECODE_ENABLE_GCC_VISIBILITY=OFF \ + -DBUILD_TESTING=OFF -DGECODE_ENABLE_EXAMPLES=OFF \ + -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS=OFF \ + -DGECODE_ENABLE_SET_VARS=OFF -DGECODE_ENABLE_FLOAT_VARS=OFF \ + -DGECODE_ENABLE_MINIMODEL=OFF -DGECODE_ENABLE_DRIVER=OFF \ + -DGECODE_ENABLE_FLATZINC=OFF -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF + cmake --build build-allocator --config "$BUILD_TYPE" --parallel 2 + cmake --install build-allocator --config "$BUILD_TYPE" --prefix "$GITHUB_WORKSPACE/install-allocator" + + - name: Check private allocator exports + if: matrix.variant == 'shared' && runner.os != 'Windows' + shell: bash + run: | + set -euxo pipefail + if [ "$RUNNER_OS" = Linux ]; then + nm -D --defined-only install-allocator/lib/libgecodesupport.so > build-allocator/exports.txt + else + nm -gU install-allocator/lib/libgecodesupport.dylib > build-allocator/exports.txt + fi + if grep -E '[[:space:]]_?(mi_[[:alnum:]_]+|malloc|calloc|realloc|free|aligned_alloc|posix_memalign)$' build-allocator/exports.txt; then + echo "Allocator implementation or malloc replacement exported by gecodesupport" + exit 1 + fi + + - name: Reject unsupported allocator combinations + if: matrix.os == 'ubuntu-latest' && matrix.variant == 'shared' && matrix.mimalloc == 'ON' + shell: bash + run: | + set -euxo pipefail + if cmake -S . -B "$RUNNER_TEMP/mimalloc-custom" \ + -DGECODE_ENABLE_MIMALLOC=ON -DGECODE_ENABLE_ALLOCATOR=OFF > "$RUNNER_TEMP/mimalloc-custom.log" 2>&1; then + exit 1 + fi + grep -q 'requires GECODE_ENABLE_ALLOCATOR=ON' "$RUNNER_TEMP/mimalloc-custom.log" + if cmake -S . -B "$RUNNER_TEMP/mimalloc-sanitizer" \ + -DGECODE_ENABLE_MIMALLOC=ON -DGECODE_SANITIZER=address > "$RUNNER_TEMP/mimalloc-sanitizer.log" 2>&1; then + exit 1 + fi + grep -q 'does not support GECODE_SANITIZER' "$RUNNER_TEMP/mimalloc-sanitizer.log" + + - name: Installed allocator ownership and thread transfer + shell: bash + run: | + set -euxo pipefail + cmake -S test/package/allocator -B build-allocator-consumer \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install-allocator" \ + -DGECODE_LINK_VARIANT=${{ matrix.variant }} \ + -DGECODE_EXPECT_MIMALLOC=${{ matrix.mimalloc }} \ + -DMIMALLOC_INCLUDE_DIR="$GITHUB_WORKSPACE/build-allocator/_deps/gecode_mimalloc-src/include" + cmake --build build-allocator-consumer --config "$BUILD_TYPE" --parallel 2 + export PATH="$GITHUB_WORKSPACE/install-allocator/bin:$PATH" + ctest --test-dir build-allocator-consumer --build-config "$BUILD_TYPE" --output-on-failure + if [ "${{ matrix.mimalloc }}" = ON ]; then + test -f install-allocator/share/licenses/gecode/mimalloc-LICENSE + test ! -f install-allocator/include/mimalloc.h + fi + build-32-bit-linkage: runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 01b980c7be..d8b8093bec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ option(GECODE_ENABLE_FLATZINC "Build FlatZinc module" ON) option(GECODE_ENABLE_MPFR "Enable MPFR support" ON) option(GECODE_ENABLE_ALLOCATOR "Enable default allocator" ON) +option(GECODE_ENABLE_MIMALLOC "Use a private mimalloc backend for the default allocator" OFF) option(GECODE_ENABLE_AUDIT "Enable audit code" OFF) # Test-only; FailPoint state is process-global and is supported only by the # dedicated isolated single-threaded fault-test executable below. @@ -774,6 +775,17 @@ if(HAVE_BUILTIN_POPCOUNTLL) set(GECODE_HAS_BUILTIN_POPCOUNTLL "/**/") endif() +if(GECODE_ENABLE_MIMALLOC) + if(NOT GECODE_ENABLE_ALLOCATOR) + message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC requires GECODE_ENABLE_ALLOCATOR=ON") + endif() + if(GECODE_SANITIZER_NORMALIZED) + message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC does not support GECODE_SANITIZER; use the system allocator for sanitizer builds") + endif() + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GecodeMimalloc.cmake) + gecode_configure_mimalloc() +endif() + # Process config.hpp using autoconf-like undef handling. list(LENGTH CONFIG length) math(EXPR length "${length} - 1") @@ -802,6 +814,10 @@ foreach(i RANGE ${length}) set(CONFIG_OUT "${CONFIG_OUT}${line}") endforeach() +if(GECODE_ENABLE_MIMALLOC) + string(APPEND CONFIG_OUT "\n/* Use the private mimalloc backend (CMake only). */\n#define GECODE_MIMALLOC 1\n") +endif() + file(CONFIGURE OUTPUT "${GECODE_BINARY_DIR}/gecode/support/config.hpp" CONTENT "/* gecode/support/config.hpp. Generated from config.hpp.in by configure. */ /* gecode/support/config.hpp.in. Generated from configure.ac by autoheader. */ @@ -1235,6 +1251,16 @@ foreach(component ${GECODE_LIBRARY_COMPONENTS}) add_gecode_component_library(${component}) endforeach() +if(GECODE_ENABLE_MIMALLOC) + foreach(kind shared static) + if(TARGET gecodesupport_${kind}) + target_sources(gecodesupport_${kind} PRIVATE $) + target_include_directories(gecodesupport_${kind} PRIVATE "${GECODE_MIMALLOC_SOURCE_DIR}/include") + target_link_libraries(gecodesupport_${kind} PRIVATE ${GECODE_MIMALLOC_LIBRARIES}) + endif() + endforeach() +endif() + if(GECODE_ENABLE_THREAD) find_package(Threads REQUIRED) gecode_link_component(support Threads::Threads) @@ -1649,6 +1675,11 @@ endif() # --------------------------------------------------------------------------- if(GECODE_INSTALL) include(CMakePackageConfigHelpers) + if(GECODE_ENABLE_MIMALLOC) + install(FILES "${GECODE_MIMALLOC_SOURCE_DIR}/LICENSE" + DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/gecode + RENAME mimalloc-LICENSE) + endif() install(TARGETS ${GECODE_INSTALL_TARGETS} EXPORT GecodeTargets @@ -1787,7 +1818,7 @@ foreach(gecode_option IN ITEMS GECODE_ENABLE_EXAMPLES GECODE_INSTALL GECODE_ENABLE_SEARCH GECODE_ENABLE_INT_VARS GECODE_ENABLE_SET_VARS GECODE_ENABLE_FLOAT_VARS GECODE_ENABLE_MINIMODEL GECODE_ENABLE_DRIVER - GECODE_ENABLE_FLATZINC GECODE_ENABLE_ALLOCATOR GECODE_ENABLE_AUDIT + GECODE_ENABLE_FLATZINC GECODE_ENABLE_ALLOCATOR GECODE_ENABLE_MIMALLOC GECODE_ENABLE_AUDIT GECODE_ENABLE_FAULT_INJECTION GECODE_REGENERATE_VARIMP GECODE_DOC_SEARCH GECODE_DOC_TAGFILE) get_property(gecode_option_description CACHE ${gecode_option} PROPERTY HELPSTRING) diff --git a/changelog.in b/changelog.in index 08a4acf47c..34b0ddc62b 100755 --- a/changelog.in +++ b/changelog.in @@ -73,6 +73,14 @@ Date: unreleased [DESCRIPTION] This is the development changelog for the next Gecode release. +[ENTRY] +Module: support +What: new +Rank: minor +[DESCRIPTION] +Add an optional CMake mimalloc backend for Gecode heap and space allocations. +The system allocator is the default. + [ENTRY] Module: kernel What: new diff --git a/cmake/GecodeMimalloc.cmake b/cmake/GecodeMimalloc.cmake new file mode 100644 index 0000000000..5a72f3a93e --- /dev/null +++ b/cmake/GecodeMimalloc.cmake @@ -0,0 +1,46 @@ +# A private source build keeps malloc/new replacement outside Gecode. +function(gecode_configure_mimalloc) + foreach(target mimalloc mimalloc-static mimalloc-obj mimalloc-obj-target) + if(TARGET ${target}) + message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC cannot share an existing ${target} target; Gecode requires its own non-overriding mimalloc build") + endif() + endforeach() + + # Normal variables keep these choices local, including when Gecode is a subproject. + # Upstream's minimum policy version predates normal-variable preservation for + # set(CACHE); keep our local settings when it declares its cache options. + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) + endif() + set(MI_OVERRIDE OFF) + set(MI_XMALLOC OFF) + set(MI_EXTRA_CPPDEFS "") + set(MI_DEBUG_TSAN OFF) + set(MI_DEBUG_UBSAN OFF) + set(MI_OSX_INTERPOSE OFF) + set(MI_OSX_ZONE OFF) + set(MI_WIN_REDIRECT OFF) + set(MI_BUILD_SHARED OFF) + set(MI_BUILD_STATIC OFF) + set(MI_BUILD_OBJECT ON) + set(MI_BUILD_TESTS OFF) + set(MI_USE_CXX DEFAULT) + set(MI_OPT_ARCH OFF) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(MI_TLS_MODEL LOCAL_DYNAMIC) + endif() + + include(FetchContent) + FetchContent_Declare(gecode_mimalloc + URL https://codeload.github.com/microsoft/mimalloc/tar.gz/refs/tags/v3.5.3 + URL_HASH SHA256=3b4a15153a59905995f7070296ed604bb5ccc00cabb8b93446931aff77224d47) + FetchContent_MakeAvailable(gecode_mimalloc) + # Exclude upstream convenience targets and install rules; embed its object directly. + set_property(DIRECTORY "${gecode_mimalloc_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) + set_target_properties(mimalloc-obj PROPERTIES C_VISIBILITY_PRESET hidden) + get_directory_property(mi_libraries DIRECTORY "${gecode_mimalloc_SOURCE_DIR}" DEFINITION mi_libraries) + set(GECODE_MIMALLOC_SOURCE_DIR "${gecode_mimalloc_SOURCE_DIR}" PARENT_SCOPE) + set(GECODE_MIMALLOC_LIBRARIES "${mi_libraries}" PARENT_SCOPE) +endfunction() diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 23f9677791..7c710237d9 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -126,6 +126,8 @@ to inspect test assignments. usable Qt GUI stack is available. `ON` requires Gist dependencies and fails configuration if they are unavailable. Gist requires Qt Gui, Widgets, and PrintSupport in addition to Qt Core. +- `GECODE_ENABLE_MIMALLOC`: selects a private mimalloc backend for Gecode's + default allocator. Disabled by default; see [Allocator backend](#allocator-backend). - `GECODE_ENABLE_FAULT_INJECTION`: builds deterministic allocation and copy failpoints together with the `Fault::*` regression suite. This facility is test-only, process-global, isolated to the dedicated single-threaded fault @@ -149,6 +151,36 @@ Subproject defaults (`add_subdirectory`): - `BUILD_TESTING=OFF` (unless enabled by parent project) - `GECODE_INSTALL=OFF` +### Allocator backend + +The default backend uses the system allocator. To build with mimalloc: + +```bash +cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON +cmake --build build-mimalloc -j +``` + +This CMake-only option downloads the pinned mimalloc 3.5.3 source and embeds it +in `gecodesupport`. It covers allocations through `Support::Allocator`, including +Gecode's heap and space storage. Application `malloc`, `new`, and standard +containers keep their normal allocator. No preload library or redirect DLL is +needed, and installed consumers need no mimalloc package or headers. + +For an offline build, provide an unpacked copy of that version with CMake's +standard source override: + +```bash +cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ + -DFETCHCONTENT_SOURCE_DIR_GECODE_MIMALLOC=/path/to/mimalloc-3.5.3 +``` + +Use matching configured headers and libraries, and rebuild consumers when +changing the backend. Mimalloc requires `GECODE_ENABLE_ALLOCATOR=ON`; custom +allocators and `GECODE_PEAKHEAP` are unsupported with this backend. Gecode's +sanitizer configurations also require the system backend, so heap diagnostics +continue to use the sanitizer's allocator interception. Autoconf builds retain +the system backend. + ### Fault injection and sanitizers Fault injection is test-only and process-global. Its tests run in a dedicated, diff --git a/gecode/support/allocator.cpp b/gecode/support/allocator.cpp index fab763faa1..8f25a509e0 100644 --- a/gecode/support/allocator.cpp +++ b/gecode/support/allocator.cpp @@ -33,10 +33,29 @@ #include +#ifdef GECODE_MIMALLOC +#include +#endif + namespace Gecode { namespace Support { Allocator allocator; +#ifdef GECODE_MIMALLOC + void* + Allocator::alloc(size_t n) { + return mi_malloc(n); + } + void* + Allocator::realloc(void* p, size_t n) { + return mi_realloc(p,n); + } + void + Allocator::free(void* p) { + mi_free(p); + } +#endif + }} // STATISTICS: support-any diff --git a/gecode/support/allocator.hpp b/gecode/support/allocator.hpp index 5477521b87..8243fe977f 100644 --- a/gecode/support/allocator.hpp +++ b/gecode/support/allocator.hpp @@ -42,6 +42,10 @@ #include #endif +#if defined(GECODE_MIMALLOC) && defined(GECODE_PEAKHEAP) +#error "The mimalloc backend does not support GECODE_PEAKHEAP" +#endif + #ifdef GECODE_ALLOCATOR namespace Gecode { namespace Support { @@ -50,6 +54,8 @@ namespace Gecode { namespace Support { * \brief Default memory allocator * * Acts as a wrapper around memory allocation functionality. + * The CMake mimalloc backend owns its allocations in gecodesupport. + * Rebuild consumers when changing the configured allocator backend. * * Can be disabled by giving a "-disable-allocator" option to * configure and the be re-implemented before including any other @@ -62,10 +68,19 @@ namespace Gecode { namespace Support { /// Default constructor Allocator(void); /// Allocate memory block of size \a n +#ifdef GECODE_MIMALLOC + GECODE_SUPPORT_EXPORT +#endif void* alloc(size_t n); /// Return address of reallocated memory block \a p of size \a n +#ifdef GECODE_MIMALLOC + GECODE_SUPPORT_EXPORT +#endif void* realloc(void* p, size_t n); /// Free memory block \a p +#ifdef GECODE_MIMALLOC + GECODE_SUPPORT_EXPORT +#endif void free(void* p); /// Copy \a n bytes from source \a s directly to \a d and returns \a d void* memcpy(void *d, const void *s, size_t n); @@ -75,6 +90,7 @@ namespace Gecode { namespace Support { forceinline Allocator::Allocator(void) { } +#ifndef GECODE_MIMALLOC forceinline void* Allocator::alloc(size_t n) { return ::malloc(n); @@ -87,6 +103,7 @@ namespace Gecode { namespace Support { Allocator::free(void* p) { ::free(p); } +#endif forceinline void* Allocator::memcpy(void *d, const void *s, size_t n) { return ::memcpy(d,s,n); diff --git a/test/package/allocator/CMakeLists.txt b/test/package/allocator/CMakeLists.txt new file mode 100644 index 0000000000..6c7f5187d8 --- /dev/null +++ b/test/package/allocator/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.21) +project(gecode_allocator_consumer LANGUAGES CXX) +enable_testing() +find_package(Gecode CONFIG REQUIRED COMPONENTS kernel) +find_package(Threads REQUIRED) + +set(GECODE_LINK_VARIANT shared CACHE STRING "Installed Gecode variant: shared or static") +option(GECODE_EXPECT_MIMALLOC "Require the mimalloc backend" OFF) +set(MIMALLOC_INCLUDE_DIR "" CACHE PATH "Private source headers for the static ownership check") +if(GECODE_LINK_VARIANT STREQUAL "shared") + add_library(allocator-boundary SHARED boundary.cpp) +elseif(GECODE_LINK_VARIANT STREQUAL "static") + add_library(allocator-boundary STATIC boundary.cpp) + target_compile_definitions(allocator-boundary PUBLIC ALLOCATOR_BOUNDARY_STATIC) +else() + message(FATAL_ERROR "GECODE_LINK_VARIANT must be shared or static") +endif() +target_compile_definitions(allocator-boundary PRIVATE ALLOCATOR_BOUNDARY_BUILD) +target_link_libraries(allocator-boundary PUBLIC Gecode::gecodekernel_${GECODE_LINK_VARIANT}) +add_executable(allocator-consumer main.cpp) +target_link_libraries(allocator-consumer PRIVATE allocator-boundary Threads::Threads) +target_compile_definitions(allocator-consumer PRIVATE GECODE_EXPECT_MIMALLOC=$) +if(GECODE_EXPECT_MIMALLOC AND GECODE_LINK_VARIANT STREQUAL "static") + if(NOT EXISTS "${MIMALLOC_INCLUDE_DIR}/mimalloc.h") + message(FATAL_ERROR "The static ownership check requires MIMALLOC_INCLUDE_DIR") + endif() + target_compile_definitions(allocator-consumer PRIVATE CHECK_MIMALLOC_OWNERSHIP) + target_include_directories(allocator-consumer PRIVATE "${MIMALLOC_INCLUDE_DIR}") +endif() +add_test(NAME allocator-consumer COMMAND allocator-consumer) diff --git a/test/package/allocator/boundary.cpp b/test/package/allocator/boundary.cpp new file mode 100644 index 0000000000..92e99addf2 --- /dev/null +++ b/test/package/allocator/boundary.cpp @@ -0,0 +1,39 @@ +#include "boundary.hh" + +void* boundary_alloc(size_t n) { + return Gecode::heap.ralloc(n); +} +void* boundary_realloc(void* p, size_t n) { + return Gecode::heap.rrealloc(p,n); +} +void boundary_free(void* p) { + Gecode::heap.rfree(p); +} + +// Space-owned data must survive cloning after the creating thread has exited. +class BufferSpace : public Gecode::Space { +public: + int* values; + BufferSpace(void) : values(alloc(128)) { + for (int i=0; i<128; i++) + values[i] = i; + } + BufferSpace(BufferSpace& s) : Gecode::Space(s), values(alloc(128)) { + for (int i=0; i<128; i++) + values[i] = s.values[i]; + } + Gecode::Space* copy(void) override { + return new BufferSpace(*this); + } +}; + +Gecode::Space* boundary_space(void) { + return new BufferSpace; +} +bool boundary_valid(Gecode::Space* s) { + BufferSpace* b = static_cast(s); + for (int i=0; i<128; i++) + if (b->values[i] != i) + return false; + return true; +} diff --git a/test/package/allocator/boundary.hh b/test/package/allocator/boundary.hh new file mode 100644 index 0000000000..9ced487140 --- /dev/null +++ b/test/package/allocator/boundary.hh @@ -0,0 +1,22 @@ +#ifndef GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH +#define GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH + +#include + +#if defined(_WIN32) && !defined(ALLOCATOR_BOUNDARY_STATIC) +#ifdef ALLOCATOR_BOUNDARY_BUILD +#define ALLOCATOR_BOUNDARY_EXPORT __declspec(dllexport) +#else +#define ALLOCATOR_BOUNDARY_EXPORT __declspec(dllimport) +#endif +#else +#define ALLOCATOR_BOUNDARY_EXPORT +#endif + +ALLOCATOR_BOUNDARY_EXPORT void* boundary_alloc(size_t n); +ALLOCATOR_BOUNDARY_EXPORT void* boundary_realloc(void* p, size_t n); +ALLOCATOR_BOUNDARY_EXPORT void boundary_free(void* p); +ALLOCATOR_BOUNDARY_EXPORT Gecode::Space* boundary_space(void); +ALLOCATOR_BOUNDARY_EXPORT bool boundary_valid(Gecode::Space* s); + +#endif diff --git a/test/package/allocator/main.cpp b/test/package/allocator/main.cpp new file mode 100644 index 0000000000..dce5ecb3aa --- /dev/null +++ b/test/package/allocator/main.cpp @@ -0,0 +1,58 @@ +#include "boundary.hh" +#include +#include +#include +#ifdef CHECK_MIMALLOC_OWNERSHIP +#include +#endif + +#if GECODE_EXPECT_MIMALLOC && !defined(GECODE_MIMALLOC) +#error "Expected mimalloc-enabled installed headers" +#elif !GECODE_EXPECT_MIMALLOC && defined(GECODE_MIMALLOC) +#error "Expected system-allocator installed headers" +#endif + +int main(void) { + void* p = Gecode::heap.ralloc(256); +#ifdef CHECK_MIMALLOC_OWNERSHIP + void* system = std::malloc(256); + int* ordinary = new int[64]; + bool isolated = mi_is_in_heap_region(p) && + !mi_is_in_heap_region(system) && !mi_is_in_heap_region(ordinary); + std::free(system); + delete[] ordinary; + if (!isolated) + return 1; +#endif + std::memset(p,0x5a,256); + std::thread resize([&] { p = boundary_realloc(p,16384); }); + resize.join(); + for (int i=0; i<256; i++) + if (static_cast(p)[i] != 0x5a) + return 2; +#ifdef CHECK_MIMALLOC_OWNERSHIP + if (!mi_is_in_heap_region(p)) + return 3; +#endif + std::thread release([&] { boundary_free(p); }); + release.join(); + std::thread allocate([&] { p = boundary_alloc(1024); }); + allocate.join(); + p = Gecode::heap.rrealloc(p,4096); + Gecode::heap.rfree(p); + + Gecode::Space* source = nullptr; + std::thread create([&] { source = boundary_space(); }); + create.join(); + if (source->status() != Gecode::SS_SOLVED) + return 4; + Gecode::Space* clone = nullptr; + std::thread copy([&] { clone = source->clone(); }); + copy.join(); + delete source; + if (!boundary_valid(clone) || clone->status() != Gecode::SS_SOLVED) + return 5; + std::thread destroy([&] { delete clone; }); + destroy.join(); + return 0; +} From e851a15a24c2afffb59a242588a614b7b1961fd0 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 18 Oct 2026 22:22:48 +0200 Subject: [PATCH 02/10] Enable integer variables in Windows allocator checks --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4488055d53..5be697be86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -286,6 +286,9 @@ jobs: set -euxo pipefail shared=OFF static=OFF + ints=OFF + # MSVC requires a variable family to avoid the kernel's zero-sized array. + if [ "$RUNNER_OS" = Windows ]; then ints=ON; fi if [ "${{ matrix.variant }}" = shared ]; then shared=ON; else static=ON; fi cmake -S . -B build-allocator \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ @@ -293,7 +296,7 @@ jobs: -DGECODE_ENABLE_MIMALLOC=${{ matrix.mimalloc }} \ -DGECODE_ENABLE_GCC_VISIBILITY=OFF \ -DBUILD_TESTING=OFF -DGECODE_ENABLE_EXAMPLES=OFF \ - -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS=OFF \ + -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS="$ints" \ -DGECODE_ENABLE_SET_VARS=OFF -DGECODE_ENABLE_FLOAT_VARS=OFF \ -DGECODE_ENABLE_MINIMODEL=OFF -DGECODE_ENABLE_DRIVER=OFF \ -DGECODE_ENABLE_FLATZINC=OFF -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF From 37e8e938e8c6cb6581b102eff2c79f84dc69ee68 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sun, 18 Oct 2026 22:30:27 +0200 Subject: [PATCH 03/10] Use supported variable families in allocator CI --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5be697be86..1e5b996e84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -286,9 +286,6 @@ jobs: set -euxo pipefail shared=OFF static=OFF - ints=OFF - # MSVC requires a variable family to avoid the kernel's zero-sized array. - if [ "$RUNNER_OS" = Windows ]; then ints=ON; fi if [ "${{ matrix.variant }}" = shared ]; then shared=ON; else static=ON; fi cmake -S . -B build-allocator \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ @@ -296,7 +293,7 @@ jobs: -DGECODE_ENABLE_MIMALLOC=${{ matrix.mimalloc }} \ -DGECODE_ENABLE_GCC_VISIBILITY=OFF \ -DBUILD_TESTING=OFF -DGECODE_ENABLE_EXAMPLES=OFF \ - -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS="$ints" \ + -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS=ON \ -DGECODE_ENABLE_SET_VARS=OFF -DGECODE_ENABLE_FLOAT_VARS=OFF \ -DGECODE_ENABLE_MINIMODEL=OFF -DGECODE_ENABLE_DRIVER=OFF \ -DGECODE_ENABLE_FLATZINC=OFF -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF From 0dcd7e5fdca916c181e566165e23684f28d97b1f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 12:10:29 +0200 Subject: [PATCH 04/10] Keep private mimalloc in shared libraries and test host coexistence --- .github/workflows/build.yml | 42 ++++++++++++++++++++------- CMakeLists.txt | 15 +++++----- changelog.in | 4 ++- docs/cmake-build.md | 28 +++++++++++++++--- test/package/allocator/CMakeLists.txt | 27 ++++++++++++----- test/package/allocator/main.cpp | 20 ++++++++----- 6 files changed, 98 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e5b996e84..acfab6962b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -270,9 +270,18 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-15, windows-2022] - variant: [shared, static] + variant: [shared] mimalloc: ["ON"] include: + - os: ubuntu-latest + variant: static + mimalloc: "OFF" + - os: macos-15 + variant: static + mimalloc: "OFF" + - os: windows-2022 + variant: static + mimalloc: "OFF" - os: ubuntu-latest variant: shared mimalloc: "OFF" @@ -325,25 +334,38 @@ jobs: exit 1 fi grep -q 'requires GECODE_ENABLE_ALLOCATOR=ON' "$RUNNER_TEMP/mimalloc-custom.log" + if cmake -S . -B "$RUNNER_TEMP/mimalloc-static" \ + -DGECODE_ENABLE_MIMALLOC=ON -DGECODE_BUILD_STATIC=ON > "$RUNNER_TEMP/mimalloc-static.log" 2>&1; then + exit 1 + fi + grep -q 'requires GECODE_BUILD_STATIC=OFF' "$RUNNER_TEMP/mimalloc-static.log" if cmake -S . -B "$RUNNER_TEMP/mimalloc-sanitizer" \ -DGECODE_ENABLE_MIMALLOC=ON -DGECODE_SANITIZER=address > "$RUNNER_TEMP/mimalloc-sanitizer.log" 2>&1; then exit 1 fi grep -q 'does not support GECODE_SANITIZER' "$RUNNER_TEMP/mimalloc-sanitizer.log" - - name: Installed allocator ownership and thread transfer + - name: Installed allocator coexistence and thread transfer shell: bash run: | set -euxo pipefail - cmake -S test/package/allocator -B build-allocator-consumer \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install-allocator" \ - -DGECODE_LINK_VARIANT=${{ matrix.variant }} \ - -DGECODE_EXPECT_MIMALLOC=${{ matrix.mimalloc }} \ - -DMIMALLOC_INCLUDE_DIR="$GITHUB_WORKSPACE/build-allocator/_deps/gecode_mimalloc-src/include" - cmake --build build-allocator-consumer --config "$BUILD_TYPE" --parallel 2 export PATH="$GITHUB_WORKSPACE/install-allocator/bin:$PATH" - ctest --test-dir build-allocator-consumer --build-config "$BUILD_TYPE" --output-on-failure + host_modes=OFF + if [ "$RUNNER_OS" != Windows ] && [ "${{ matrix.mimalloc }}" = ON ]; then + host_modes="OFF ON" + fi + for host_override in $host_modes; do + consumer="build-allocator-consumer-$host_override" + cmake -S test/package/allocator -B "$consumer" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install-allocator" \ + -DGECODE_LINK_VARIANT=${{ matrix.variant }} \ + -DGECODE_EXPECT_MIMALLOC=${{ matrix.mimalloc }} \ + -DGECODE_HOST_MIMALLOC_OVERRIDE="$host_override" \ + -DMIMALLOC_SOURCE_DIR="$GITHUB_WORKSPACE/build-allocator/_deps/gecode_mimalloc-src" + cmake --build "$consumer" --config "$BUILD_TYPE" --parallel 2 + ctest --test-dir "$consumer" --build-config "$BUILD_TYPE" --output-on-failure + done if [ "${{ matrix.mimalloc }}" = ON ]; then test -f install-allocator/share/licenses/gecode/mimalloc-LICENSE test ! -f install-allocator/include/mimalloc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8b8093bec..0767a92df6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ option(GECODE_ENABLE_FLATZINC "Build FlatZinc module" ON) option(GECODE_ENABLE_MPFR "Enable MPFR support" ON) option(GECODE_ENABLE_ALLOCATOR "Enable default allocator" ON) -option(GECODE_ENABLE_MIMALLOC "Use a private mimalloc backend for the default allocator" OFF) +option(GECODE_ENABLE_MIMALLOC "Use a private mimalloc backend for shared libraries" OFF) option(GECODE_ENABLE_AUDIT "Enable audit code" OFF) # Test-only; FailPoint state is process-global and is supported only by the # dedicated isolated single-threaded fault-test executable below. @@ -779,6 +779,9 @@ if(GECODE_ENABLE_MIMALLOC) if(NOT GECODE_ENABLE_ALLOCATOR) message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC requires GECODE_ENABLE_ALLOCATOR=ON") endif() + if(GECODE_BUILD_STATIC) + message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC requires GECODE_BUILD_STATIC=OFF; static builds cannot isolate the private mimalloc symbols") + endif() if(GECODE_SANITIZER_NORMALIZED) message(FATAL_ERROR "GECODE_ENABLE_MIMALLOC does not support GECODE_SANITIZER; use the system allocator for sanitizer builds") endif() @@ -1252,13 +1255,9 @@ foreach(component ${GECODE_LIBRARY_COMPONENTS}) endforeach() if(GECODE_ENABLE_MIMALLOC) - foreach(kind shared static) - if(TARGET gecodesupport_${kind}) - target_sources(gecodesupport_${kind} PRIVATE $) - target_include_directories(gecodesupport_${kind} PRIVATE "${GECODE_MIMALLOC_SOURCE_DIR}/include") - target_link_libraries(gecodesupport_${kind} PRIVATE ${GECODE_MIMALLOC_LIBRARIES}) - endif() - endforeach() + target_sources(gecodesupport_shared PRIVATE $) + target_include_directories(gecodesupport_shared PRIVATE "${GECODE_MIMALLOC_SOURCE_DIR}/include") + target_link_libraries(gecodesupport_shared PRIVATE ${GECODE_MIMALLOC_LIBRARIES}) endif() if(GECODE_ENABLE_THREAD) diff --git a/changelog.in b/changelog.in index 34b0ddc62b..f9bfc180c3 100755 --- a/changelog.in +++ b/changelog.in @@ -78,7 +78,9 @@ Module: support What: new Rank: minor [DESCRIPTION] -Add an optional CMake mimalloc backend for Gecode heap and space allocations. +Add an optional CMake mimalloc backend for heap and space allocations in shared +Gecode libraries. Static builds retain the system backend to avoid conflicts +with an application's mimalloc. The system allocator is the default. [ENTRY] diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 7c710237d9..4d27fb6b88 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -156,16 +156,36 @@ Subproject defaults (`add_subdirectory`): The default backend uses the system allocator. To build with mimalloc: ```bash -cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON +cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ + -DGECODE_BUILD_SHARED=ON -DGECODE_BUILD_STATIC=OFF cmake --build build-mimalloc -j ``` This CMake-only option downloads the pinned mimalloc 3.5.3 source and embeds it -in `gecodesupport`. It covers allocations through `Support::Allocator`, including -Gecode's heap and space storage. Application `malloc`, `new`, and standard -containers keep their normal allocator. No preload library or redirect DLL is +in the shared `gecodesupport` library. It covers allocations through +`Support::Allocator`, including Gecode's heap and space storage. Gecode does not +replace application `malloc` or `new`. No preload library or redirect DLL is needed, and installed consumers need no mimalloc package or headers. +The private backend requires `GECODE_BUILD_STATIC=OFF`. Static archives cannot +hide mimalloc's symbols from another statically linked copy, which can select +the wrong allocator version or cause duplicate-symbol errors. This also applies +when building shared and static Gecode libraries together. + +Current libminizinc builds embed their own mimalloc. Shared Gecode with this +backend keeps a separate allocator and can coexist with MiniZinc's copy, whether +MiniZinc enables process-wide replacement or uses only the explicit `mi_*` API. +For static Gecode, leave `GECODE_ENABLE_MIMALLOC=OFF`. With MiniZinc's +`MZN_MIMALLOC_OVERRIDE=ON`, Gecode's system allocation calls already use the +application's mimalloc; with replacement disabled, they use the system allocator. +MiniZinc currently defaults to replacement on Unix and disables it on Windows. +See [libminizinc's allocator options](https://github.com/MiniZinc/libminizinc/blob/216b0971b322f5f5960e96dbb97ace2ed8b69763/CMakeLists.txt#L20-L35). + +These combinations use an installed Gecode package. Building both projects in +one CMake source tree with their separate mimalloc builds is unsupported: the +upstream mimalloc targets have the same names. Gecode rejects pre-existing +mimalloc targets rather than changing the application's allocator settings. + For an offline build, provide an unpacked copy of that version with CMake's standard source override: diff --git a/test/package/allocator/CMakeLists.txt b/test/package/allocator/CMakeLists.txt index 6c7f5187d8..e99fefdf12 100644 --- a/test/package/allocator/CMakeLists.txt +++ b/test/package/allocator/CMakeLists.txt @@ -1,12 +1,13 @@ cmake_minimum_required(VERSION 3.21) -project(gecode_allocator_consumer LANGUAGES CXX) +project(gecode_allocator_consumer LANGUAGES C CXX) enable_testing() find_package(Gecode CONFIG REQUIRED COMPONENTS kernel) find_package(Threads REQUIRED) set(GECODE_LINK_VARIANT shared CACHE STRING "Installed Gecode variant: shared or static") option(GECODE_EXPECT_MIMALLOC "Require the mimalloc backend" OFF) -set(MIMALLOC_INCLUDE_DIR "" CACHE PATH "Private source headers for the static ownership check") +set(MIMALLOC_SOURCE_DIR "" CACHE PATH "Mimalloc sources for the host allocator coexistence check") +option(GECODE_HOST_MIMALLOC_OVERRIDE "Let the host mimalloc replace malloc" OFF) if(GECODE_LINK_VARIANT STREQUAL "shared") add_library(allocator-boundary SHARED boundary.cpp) elseif(GECODE_LINK_VARIANT STREQUAL "static") @@ -20,11 +21,23 @@ target_link_libraries(allocator-boundary PUBLIC Gecode::gecodekernel_${GECODE_LI add_executable(allocator-consumer main.cpp) target_link_libraries(allocator-consumer PRIVATE allocator-boundary Threads::Threads) target_compile_definitions(allocator-consumer PRIVATE GECODE_EXPECT_MIMALLOC=$) -if(GECODE_EXPECT_MIMALLOC AND GECODE_LINK_VARIANT STREQUAL "static") - if(NOT EXISTS "${MIMALLOC_INCLUDE_DIR}/mimalloc.h") - message(FATAL_ERROR "The static ownership check requires MIMALLOC_INCLUDE_DIR") +if(GECODE_EXPECT_MIMALLOC) + if(NOT EXISTS "${MIMALLOC_SOURCE_DIR}/include/mimalloc.h") + message(FATAL_ERROR "The host allocator check requires MIMALLOC_SOURCE_DIR") endif() - target_compile_definitions(allocator-consumer PRIVATE CHECK_MIMALLOC_OWNERSHIP) - target_include_directories(allocator-consumer PRIVATE "${MIMALLOC_INCLUDE_DIR}") + # Like libminizinc, the consumer embeds a separate mimalloc object. On macOS, + # an archive can omit the malloc-zone registration needed for replacement. + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + set(MI_OVERRIDE ${GECODE_HOST_MIMALLOC_OVERRIDE}) + set(MI_BUILD_SHARED OFF) + set(MI_BUILD_STATIC OFF) + set(MI_BUILD_OBJECT ON) + set(MI_BUILD_TESTS OFF) + set(MI_OPT_ARCH OFF) + add_subdirectory("${MIMALLOC_SOURCE_DIR}" host-mimalloc EXCLUDE_FROM_ALL) + get_directory_property(host_mi_libraries DIRECTORY "${MIMALLOC_SOURCE_DIR}" DEFINITION mi_libraries) + target_link_libraries(allocator-consumer PRIVATE mimalloc-obj ${host_mi_libraries}) + target_compile_definitions(allocator-consumer PRIVATE CHECK_HOST_MIMALLOC + GECODE_HOST_MIMALLOC_OVERRIDE=$) endif() add_test(NAME allocator-consumer COMMAND allocator-consumer) diff --git a/test/package/allocator/main.cpp b/test/package/allocator/main.cpp index dce5ecb3aa..36ff0f7239 100644 --- a/test/package/allocator/main.cpp +++ b/test/package/allocator/main.cpp @@ -2,7 +2,7 @@ #include #include #include -#ifdef CHECK_MIMALLOC_OWNERSHIP +#ifdef CHECK_HOST_MIMALLOC #include #endif @@ -14,13 +14,17 @@ int main(void) { void* p = Gecode::heap.ralloc(256); -#ifdef CHECK_MIMALLOC_OWNERSHIP +#ifdef CHECK_HOST_MIMALLOC + void* host = mi_malloc(256); void* system = std::malloc(256); - int* ordinary = new int[64]; - bool isolated = mi_is_in_heap_region(p) && - !mi_is_in_heap_region(system) && !mi_is_in_heap_region(ordinary); + bool isolated = !mi_is_in_heap_region(p) && mi_is_in_heap_region(host); +#if GECODE_HOST_MIMALLOC_OVERRIDE + isolated = isolated && mi_is_in_heap_region(system); +#else + isolated = isolated && !mi_is_in_heap_region(system); +#endif + mi_free(host); std::free(system); - delete[] ordinary; if (!isolated) return 1; #endif @@ -30,8 +34,8 @@ int main(void) { for (int i=0; i<256; i++) if (static_cast(p)[i] != 0x5a) return 2; -#ifdef CHECK_MIMALLOC_OWNERSHIP - if (!mi_is_in_heap_region(p)) +#ifdef CHECK_HOST_MIMALLOC + if (mi_is_in_heap_region(p)) return 3; #endif std::thread release([&] { boundary_free(p); }); From c2e7ea3d4d7cc3bc515575e34cb94a94816def8b Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:09:41 +0200 Subject: [PATCH 05/10] Trim mimalloc documentation to build and compatibility essentials --- changelog.in | 6 ++--- docs/cmake-build.md | 52 ++++++++++-------------------------- gecode/support/allocator.hpp | 2 -- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/changelog.in b/changelog.in index f9bfc180c3..32ade44511 100755 --- a/changelog.in +++ b/changelog.in @@ -78,10 +78,8 @@ Module: support What: new Rank: minor [DESCRIPTION] -Add an optional CMake mimalloc backend for heap and space allocations in shared -Gecode libraries. Static builds retain the system backend to avoid conflicts -with an application's mimalloc. -The system allocator is the default. +Add an optional CMake mimalloc backend for shared Gecode libraries. +The system allocator remains the default. [ENTRY] Module: kernel diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 4d27fb6b88..90203c0d08 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -153,7 +153,7 @@ Subproject defaults (`add_subdirectory`): ### Allocator backend -The default backend uses the system allocator. To build with mimalloc: +The system allocator is the default. To enable the CMake-only mimalloc backend: ```bash cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ @@ -161,45 +161,21 @@ cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ cmake --build build-mimalloc -j ``` -This CMake-only option downloads the pinned mimalloc 3.5.3 source and embeds it -in the shared `gecodesupport` library. It covers allocations through -`Support::Allocator`, including Gecode's heap and space storage. Gecode does not -replace application `malloc` or `new`. No preload library or redirect DLL is -needed, and installed consumers need no mimalloc package or headers. - -The private backend requires `GECODE_BUILD_STATIC=OFF`. Static archives cannot -hide mimalloc's symbols from another statically linked copy, which can select -the wrong allocator version or cause duplicate-symbol errors. This also applies -when building shared and static Gecode libraries together. - -Current libminizinc builds embed their own mimalloc. Shared Gecode with this -backend keeps a separate allocator and can coexist with MiniZinc's copy, whether -MiniZinc enables process-wide replacement or uses only the explicit `mi_*` API. -For static Gecode, leave `GECODE_ENABLE_MIMALLOC=OFF`. With MiniZinc's -`MZN_MIMALLOC_OVERRIDE=ON`, Gecode's system allocation calls already use the -application's mimalloc; with replacement disabled, they use the system allocator. -MiniZinc currently defaults to replacement on Unix and disables it on Windows. -See [libminizinc's allocator options](https://github.com/MiniZinc/libminizinc/blob/216b0971b322f5f5960e96dbb97ace2ed8b69763/CMakeLists.txt#L20-L35). - -These combinations use an installed Gecode package. Building both projects in -one CMake source tree with their separate mimalloc builds is unsupported: the -upstream mimalloc targets have the same names. Gecode rejects pre-existing -mimalloc targets rather than changing the application's allocator settings. - -For an offline build, provide an unpacked copy of that version with CMake's -standard source override: +This downloads mimalloc 3.5.3 and embeds it in shared `gecodesupport` for Gecode +heap and space storage. Application `malloc` and `new` are unaffected, and +installed consumers need no mimalloc package. -```bash -cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ - -DFETCHCONTENT_SOURCE_DIR_GECODE_MIMALLOC=/path/to/mimalloc-3.5.3 -``` +Static builds are unsupported because they cannot isolate mimalloc's symbols. +The backend requires `GECODE_ENABLE_ALLOCATOR=ON` and does not support +`GECODE_SANITIZER` or `GECODE_PEAKHEAP`. Rebuild consumers when changing backends. + +Shared Gecode can coexist with libminizinc's mimalloc. For static Gecode, keep +this option off; MiniZinc's process-wide replacement, when enabled, already +covers Gecode's system allocation calls. -Use matching configured headers and libraries, and rebuild consumers when -changing the backend. Mimalloc requires `GECODE_ENABLE_ALLOCATOR=ON`; custom -allocators and `GECODE_PEAKHEAP` are unsupported with this backend. Gecode's -sanitizer configurations also require the system backend, so heap diagnostics -continue to use the sanitizer's allocator interception. Autoconf builds retain -the system backend. +For offline builds, set `FETCHCONTENT_SOURCE_DIR_GECODE_MIMALLOC` to an unpacked +mimalloc 3.5.3 source directory. Separate mimalloc source builds in one CMake +tree are unsupported because their target names conflict. ### Fault injection and sanitizers diff --git a/gecode/support/allocator.hpp b/gecode/support/allocator.hpp index 8243fe977f..301c654c59 100644 --- a/gecode/support/allocator.hpp +++ b/gecode/support/allocator.hpp @@ -54,8 +54,6 @@ namespace Gecode { namespace Support { * \brief Default memory allocator * * Acts as a wrapper around memory allocation functionality. - * The CMake mimalloc backend owns its allocations in gecodesupport. - * Rebuild consumers when changing the configured allocator backend. * * Can be disabled by giving a "-disable-allocator" option to * configure and the be re-implemented before including any other From a350006d9e7835013dee9155ed7993bcd1c95643 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:10:45 +0200 Subject: [PATCH 06/10] Simplify allocator documentation wording --- changelog.in | 2 +- docs/cmake-build.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/changelog.in b/changelog.in index 32ade44511..b2f6de506d 100755 --- a/changelog.in +++ b/changelog.in @@ -79,7 +79,7 @@ What: new Rank: minor [DESCRIPTION] Add an optional CMake mimalloc backend for shared Gecode libraries. -The system allocator remains the default. +The system allocator is the default. [ENTRY] Module: kernel diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 90203c0d08..4676e6a3e3 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -161,17 +161,17 @@ cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ cmake --build build-mimalloc -j ``` -This downloads mimalloc 3.5.3 and embeds it in shared `gecodesupport` for Gecode -heap and space storage. Application `malloc` and `new` are unaffected, and -installed consumers need no mimalloc package. +The option downloads mimalloc 3.5.3 and embeds it in shared `gecodesupport` +for Gecode heap and space storage. Application `malloc` and `new` are +unaffected, and installed consumers need no mimalloc package. Static builds are unsupported because they cannot isolate mimalloc's symbols. The backend requires `GECODE_ENABLE_ALLOCATOR=ON` and does not support `GECODE_SANITIZER` or `GECODE_PEAKHEAP`. Rebuild consumers when changing backends. -Shared Gecode can coexist with libminizinc's mimalloc. For static Gecode, keep -this option off; MiniZinc's process-wide replacement, when enabled, already -covers Gecode's system allocation calls. +Shared Gecode can coexist with libminizinc's mimalloc. For static Gecode, use +the system backend. MiniZinc's process-wide replacement also covers these +allocations when enabled. For offline builds, set `FETCHCONTENT_SOURCE_DIR_GECODE_MIMALLOC` to an unpacked mimalloc 3.5.3 source directory. Separate mimalloc source builds in one CMake From 74259350db8c41ad3e334643ca4261b3ec97d03f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:14:58 +0200 Subject: [PATCH 07/10] Keep allocator package checks focused on heap ownership --- test/package/allocator/CMakeLists.txt | 4 ++-- test/package/allocator/boundary.cpp | 28 --------------------------- test/package/allocator/boundary.hh | 4 +--- test/package/allocator/main.cpp | 13 ------------- 4 files changed, 3 insertions(+), 46 deletions(-) diff --git a/test/package/allocator/CMakeLists.txt b/test/package/allocator/CMakeLists.txt index e99fefdf12..9c76842391 100644 --- a/test/package/allocator/CMakeLists.txt +++ b/test/package/allocator/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.21) project(gecode_allocator_consumer LANGUAGES C CXX) enable_testing() -find_package(Gecode CONFIG REQUIRED COMPONENTS kernel) +find_package(Gecode CONFIG REQUIRED COMPONENTS support) find_package(Threads REQUIRED) set(GECODE_LINK_VARIANT shared CACHE STRING "Installed Gecode variant: shared or static") @@ -17,7 +17,7 @@ else() message(FATAL_ERROR "GECODE_LINK_VARIANT must be shared or static") endif() target_compile_definitions(allocator-boundary PRIVATE ALLOCATOR_BOUNDARY_BUILD) -target_link_libraries(allocator-boundary PUBLIC Gecode::gecodekernel_${GECODE_LINK_VARIANT}) +target_link_libraries(allocator-boundary PUBLIC Gecode::gecodesupport_${GECODE_LINK_VARIANT}) add_executable(allocator-consumer main.cpp) target_link_libraries(allocator-consumer PRIVATE allocator-boundary Threads::Threads) target_compile_definitions(allocator-consumer PRIVATE GECODE_EXPECT_MIMALLOC=$) diff --git a/test/package/allocator/boundary.cpp b/test/package/allocator/boundary.cpp index 92e99addf2..9a9f7b34eb 100644 --- a/test/package/allocator/boundary.cpp +++ b/test/package/allocator/boundary.cpp @@ -9,31 +9,3 @@ void* boundary_realloc(void* p, size_t n) { void boundary_free(void* p) { Gecode::heap.rfree(p); } - -// Space-owned data must survive cloning after the creating thread has exited. -class BufferSpace : public Gecode::Space { -public: - int* values; - BufferSpace(void) : values(alloc(128)) { - for (int i=0; i<128; i++) - values[i] = i; - } - BufferSpace(BufferSpace& s) : Gecode::Space(s), values(alloc(128)) { - for (int i=0; i<128; i++) - values[i] = s.values[i]; - } - Gecode::Space* copy(void) override { - return new BufferSpace(*this); - } -}; - -Gecode::Space* boundary_space(void) { - return new BufferSpace; -} -bool boundary_valid(Gecode::Space* s) { - BufferSpace* b = static_cast(s); - for (int i=0; i<128; i++) - if (b->values[i] != i) - return false; - return true; -} diff --git a/test/package/allocator/boundary.hh b/test/package/allocator/boundary.hh index 9ced487140..124b95c5cc 100644 --- a/test/package/allocator/boundary.hh +++ b/test/package/allocator/boundary.hh @@ -1,7 +1,7 @@ #ifndef GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH #define GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH -#include +#include #if defined(_WIN32) && !defined(ALLOCATOR_BOUNDARY_STATIC) #ifdef ALLOCATOR_BOUNDARY_BUILD @@ -16,7 +16,5 @@ ALLOCATOR_BOUNDARY_EXPORT void* boundary_alloc(size_t n); ALLOCATOR_BOUNDARY_EXPORT void* boundary_realloc(void* p, size_t n); ALLOCATOR_BOUNDARY_EXPORT void boundary_free(void* p); -ALLOCATOR_BOUNDARY_EXPORT Gecode::Space* boundary_space(void); -ALLOCATOR_BOUNDARY_EXPORT bool boundary_valid(Gecode::Space* s); #endif diff --git a/test/package/allocator/main.cpp b/test/package/allocator/main.cpp index 36ff0f7239..3d565a779c 100644 --- a/test/package/allocator/main.cpp +++ b/test/package/allocator/main.cpp @@ -45,18 +45,5 @@ int main(void) { p = Gecode::heap.rrealloc(p,4096); Gecode::heap.rfree(p); - Gecode::Space* source = nullptr; - std::thread create([&] { source = boundary_space(); }); - create.join(); - if (source->status() != Gecode::SS_SOLVED) - return 4; - Gecode::Space* clone = nullptr; - std::thread copy([&] { clone = source->clone(); }); - copy.join(); - delete source; - if (!boundary_valid(clone) || clone->status() != Gecode::SS_SOLVED) - return 5; - std::thread destroy([&] { delete clone; }); - destroy.join(); return 0; } From 3ba2ff52352bd4be2fc231a9891574acd36bf60c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:25:46 +0200 Subject: [PATCH 08/10] Reduce mimalloc documentation to the option reference --- docs/cmake-build.md | 33 ++++----------------------- test/package/allocator/CMakeLists.txt | 3 +-- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/docs/cmake-build.md b/docs/cmake-build.md index 4676e6a3e3..6136adc6e5 100644 --- a/docs/cmake-build.md +++ b/docs/cmake-build.md @@ -126,8 +126,11 @@ to inspect test assignments. usable Qt GUI stack is available. `ON` requires Gist dependencies and fails configuration if they are unavailable. Gist requires Qt Gui, Widgets, and PrintSupport in addition to Qt Core. -- `GECODE_ENABLE_MIMALLOC`: selects a private mimalloc backend for Gecode's - default allocator. Disabled by default; see [Allocator backend](#allocator-backend). +- `GECODE_ENABLE_MIMALLOC`: downloads and embeds mimalloc 3.5.3 for Gecode heap + and space storage. Default `OFF`. Requires `GECODE_BUILD_STATIC=OFF` and + `GECODE_ENABLE_ALLOCATOR=ON`; incompatible with `GECODE_SANITIZER` and + `GECODE_PEAKHEAP`. Application `malloc` and `new` are unaffected. + Rebuild consumers when changing backends. - `GECODE_ENABLE_FAULT_INJECTION`: builds deterministic allocation and copy failpoints together with the `Fault::*` regression suite. This facility is test-only, process-global, isolated to the dedicated single-threaded fault @@ -151,32 +154,6 @@ Subproject defaults (`add_subdirectory`): - `BUILD_TESTING=OFF` (unless enabled by parent project) - `GECODE_INSTALL=OFF` -### Allocator backend - -The system allocator is the default. To enable the CMake-only mimalloc backend: - -```bash -cmake -S . -B build-mimalloc -DGECODE_ENABLE_MIMALLOC=ON \ - -DGECODE_BUILD_SHARED=ON -DGECODE_BUILD_STATIC=OFF -cmake --build build-mimalloc -j -``` - -The option downloads mimalloc 3.5.3 and embeds it in shared `gecodesupport` -for Gecode heap and space storage. Application `malloc` and `new` are -unaffected, and installed consumers need no mimalloc package. - -Static builds are unsupported because they cannot isolate mimalloc's symbols. -The backend requires `GECODE_ENABLE_ALLOCATOR=ON` and does not support -`GECODE_SANITIZER` or `GECODE_PEAKHEAP`. Rebuild consumers when changing backends. - -Shared Gecode can coexist with libminizinc's mimalloc. For static Gecode, use -the system backend. MiniZinc's process-wide replacement also covers these -allocations when enabled. - -For offline builds, set `FETCHCONTENT_SOURCE_DIR_GECODE_MIMALLOC` to an unpacked -mimalloc 3.5.3 source directory. Separate mimalloc source builds in one CMake -tree are unsupported because their target names conflict. - ### Fault injection and sanitizers Fault injection is test-only and process-global. Its tests run in a dedicated, diff --git a/test/package/allocator/CMakeLists.txt b/test/package/allocator/CMakeLists.txt index 9c76842391..caa1cb579a 100644 --- a/test/package/allocator/CMakeLists.txt +++ b/test/package/allocator/CMakeLists.txt @@ -25,8 +25,7 @@ if(GECODE_EXPECT_MIMALLOC) if(NOT EXISTS "${MIMALLOC_SOURCE_DIR}/include/mimalloc.h") message(FATAL_ERROR "The host allocator check requires MIMALLOC_SOURCE_DIR") endif() - # Like libminizinc, the consumer embeds a separate mimalloc object. On macOS, - # an archive can omit the malloc-zone registration needed for replacement. + # Embed the complete object so macOS includes malloc-zone registration. set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) set(MI_OVERRIDE ${GECODE_HOST_MIMALLOC_OVERRIDE}) set(MI_BUILD_SHARED OFF) From 42feb0fb5cf2e5ced350c06bc81a6eaf1a237e63 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:26:09 +0200 Subject: [PATCH 09/10] Mention parallel search performance in mimalloc changelog --- changelog.in | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.in b/changelog.in index b2f6de506d..89b00d12bb 100755 --- a/changelog.in +++ b/changelog.in @@ -79,6 +79,7 @@ What: new Rank: minor [DESCRIPTION] Add an optional CMake mimalloc backend for shared Gecode libraries. +Mimalloc can substantially improve parallel search performance on some workloads. The system allocator is the default. [ENTRY] From 79ed9b24dd2f414376ef2043c1fe78434529b377 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 20 Oct 2026 13:44:36 +0200 Subject: [PATCH 10/10] Use one Linux job for mimalloc CI coverage --- .github/workflows/build.yml | 69 ++++++++----------------------------- 1 file changed, 15 insertions(+), 54 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index acfab6962b..e2a4e69cc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -265,26 +265,7 @@ jobs: bash .github/ci/flatzinc-tools-smoke.sh "$GITHUB_WORKSPACE/install-ninja" build-cmake-allocator: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-15, windows-2022] - variant: [shared] - mimalloc: ["ON"] - include: - - os: ubuntu-latest - variant: static - mimalloc: "OFF" - - os: macos-15 - variant: static - mimalloc: "OFF" - - os: windows-2022 - variant: static - mimalloc: "OFF" - - os: ubuntu-latest - variant: shared - mimalloc: "OFF" + runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 @@ -293,16 +274,13 @@ jobs: shell: bash run: | set -euxo pipefail - shared=OFF - static=OFF - if [ "${{ matrix.variant }}" = shared ]; then shared=ON; else static=ON; fi cmake -S . -B build-allocator \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DGECODE_BUILD_SHARED="$shared" -DGECODE_BUILD_STATIC="$static" \ - -DGECODE_ENABLE_MIMALLOC=${{ matrix.mimalloc }} \ + -DGECODE_BUILD_SHARED=ON -DGECODE_BUILD_STATIC=OFF \ + -DGECODE_ENABLE_MIMALLOC=ON \ -DGECODE_ENABLE_GCC_VISIBILITY=OFF \ -DBUILD_TESTING=OFF -DGECODE_ENABLE_EXAMPLES=OFF \ - -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS=ON \ + -DGECODE_ENABLE_SEARCH=OFF -DGECODE_ENABLE_INT_VARS=OFF \ -DGECODE_ENABLE_SET_VARS=OFF -DGECODE_ENABLE_FLOAT_VARS=OFF \ -DGECODE_ENABLE_MINIMODEL=OFF -DGECODE_ENABLE_DRIVER=OFF \ -DGECODE_ENABLE_FLATZINC=OFF -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF @@ -310,22 +288,16 @@ jobs: cmake --install build-allocator --config "$BUILD_TYPE" --prefix "$GITHUB_WORKSPACE/install-allocator" - name: Check private allocator exports - if: matrix.variant == 'shared' && runner.os != 'Windows' shell: bash run: | set -euxo pipefail - if [ "$RUNNER_OS" = Linux ]; then - nm -D --defined-only install-allocator/lib/libgecodesupport.so > build-allocator/exports.txt - else - nm -gU install-allocator/lib/libgecodesupport.dylib > build-allocator/exports.txt - fi + nm -D --defined-only install-allocator/lib/libgecodesupport.so > build-allocator/exports.txt if grep -E '[[:space:]]_?(mi_[[:alnum:]_]+|malloc|calloc|realloc|free|aligned_alloc|posix_memalign)$' build-allocator/exports.txt; then echo "Allocator implementation or malloc replacement exported by gecodesupport" exit 1 fi - name: Reject unsupported allocator combinations - if: matrix.os == 'ubuntu-latest' && matrix.variant == 'shared' && matrix.mimalloc == 'ON' shell: bash run: | set -euxo pipefail @@ -349,27 +321,16 @@ jobs: shell: bash run: | set -euxo pipefail - export PATH="$GITHUB_WORKSPACE/install-allocator/bin:$PATH" - host_modes=OFF - if [ "$RUNNER_OS" != Windows ] && [ "${{ matrix.mimalloc }}" = ON ]; then - host_modes="OFF ON" - fi - for host_override in $host_modes; do - consumer="build-allocator-consumer-$host_override" - cmake -S test/package/allocator -B "$consumer" \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install-allocator" \ - -DGECODE_LINK_VARIANT=${{ matrix.variant }} \ - -DGECODE_EXPECT_MIMALLOC=${{ matrix.mimalloc }} \ - -DGECODE_HOST_MIMALLOC_OVERRIDE="$host_override" \ - -DMIMALLOC_SOURCE_DIR="$GITHUB_WORKSPACE/build-allocator/_deps/gecode_mimalloc-src" - cmake --build "$consumer" --config "$BUILD_TYPE" --parallel 2 - ctest --test-dir "$consumer" --build-config "$BUILD_TYPE" --output-on-failure - done - if [ "${{ matrix.mimalloc }}" = ON ]; then - test -f install-allocator/share/licenses/gecode/mimalloc-LICENSE - test ! -f install-allocator/include/mimalloc.h - fi + cmake -S test/package/allocator -B build-allocator-consumer \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install-allocator" \ + -DGECODE_LINK_VARIANT=shared -DGECODE_EXPECT_MIMALLOC=ON \ + -DGECODE_HOST_MIMALLOC_OVERRIDE=ON \ + -DMIMALLOC_SOURCE_DIR="$GITHUB_WORKSPACE/build-allocator/_deps/gecode_mimalloc-src" + cmake --build build-allocator-consumer --config "$BUILD_TYPE" --parallel 2 + ctest --test-dir build-allocator-consumer --build-config "$BUILD_TYPE" --output-on-failure + test -f install-allocator/share/licenses/gecode/mimalloc-LICENSE + test ! -f install-allocator/include/mimalloc.h build-32-bit-linkage: runs-on: ubuntu-latest