Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,74 @@ 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: ubuntu-latest

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0

- name: Build and install allocator package
shell: bash
run: |
set -euxo pipefail
cmake -S . -B build-allocator \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-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=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
shell: bash
run: |
set -euxo pipefail
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
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-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 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=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

Expand Down
32 changes: 31 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.
Expand Down Expand Up @@ -774,6 +775,20 @@ 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_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()
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")
Expand Down Expand Up @@ -802,6 +817,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. */
Expand Down Expand Up @@ -1235,6 +1254,12 @@ foreach(component ${GECODE_LIBRARY_COMPONENTS})
add_gecode_component_library(${component})
endforeach()

if(GECODE_ENABLE_MIMALLOC)
target_sources(gecodesupport_shared PRIVATE $<TARGET_OBJECTS:mimalloc-obj>)
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)
find_package(Threads REQUIRED)
gecode_link_component(support Threads::Threads)
Expand Down Expand Up @@ -1649,6 +1674,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
Expand Down Expand Up @@ -1787,7 +1817,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)
Expand Down
9 changes: 9 additions & 0 deletions changelog.in
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ 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 shared Gecode libraries.
Mimalloc can substantially improve parallel search performance on some workloads.
The system allocator is the default.

[ENTRY]
Module: kernel
What: new
Expand Down
46 changes: 46 additions & 0 deletions cmake/GecodeMimalloc.cmake
Original file line number Diff line number Diff line change
@@ -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()
5 changes: 5 additions & 0 deletions docs/cmake-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +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`: 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
Expand Down
19 changes: 19 additions & 0 deletions gecode/support/allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,29 @@

#include <gecode/support.hh>

#ifdef GECODE_MIMALLOC
#include <mimalloc.h>
#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
15 changes: 15 additions & 0 deletions gecode/support/allocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
#include <malloc/malloc.h>
#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 {
Expand All @@ -62,10 +66,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);
Expand All @@ -75,6 +88,7 @@ namespace Gecode { namespace Support {
forceinline
Allocator::Allocator(void) {
}
#ifndef GECODE_MIMALLOC
forceinline void*
Allocator::alloc(size_t n) {
return ::malloc(n);
Expand All @@ -87,6 +101,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);
Expand Down
42 changes: 42 additions & 0 deletions test/package/allocator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.21)
project(gecode_allocator_consumer LANGUAGES C CXX)
enable_testing()
find_package(Gecode CONFIG REQUIRED COMPONENTS support)
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_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")
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::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=$<BOOL:${GECODE_EXPECT_MIMALLOC}>)
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()
# 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)
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=$<BOOL:${GECODE_HOST_MIMALLOC_OVERRIDE}>)
endif()
add_test(NAME allocator-consumer COMMAND allocator-consumer)
11 changes: 11 additions & 0 deletions test/package/allocator/boundary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#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);
}
20 changes: 20 additions & 0 deletions test/package/allocator/boundary.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH
#define GECODE_TEST_PACKAGE_ALLOCATOR_BOUNDARY_HH

#include <gecode/support.hh>

#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);

#endif
Loading
Loading