[
  {
    "path": ".gitattributes",
    "content": "# default behavior is to always use unix style line endings\n* text eol=lf\n*.png binary\n*.pdn binary\n*.jpg binary\n*.sln binary\n*.suo binary\n*.vcproj binary\n*.patch binary\n*.dll binary\n*.lib binary\n*.exe binary\n"
  },
  {
    "path": ".gitignore",
    "content": "build\nide/vs20??/*.db\nide/vs20??/*.opendb\nide/vs20??/*.user\nide/vs20??/.vs\nide/vs20??/VTune*\nout/\ndocs/\n*.zip\n*.tar\n*.gz\n.vscode\n.DS_STore\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18)\nproject(libmimalloc C CXX)\n\nset(CMAKE_C_STANDARD 11)\nset(CMAKE_CXX_STANDARD 17)\n\noption(MI_SECURE            \"Use full security mitigations (like guard pages, allocation randomization, double-free mitigation, and free-list corruption detection)\" OFF)\noption(MI_PADDING           \"Enable padding to detect heap block overflow (always on in DEBUG or SECURE mode, or with Valgrind/ASAN)\" OFF)\noption(MI_OVERRIDE          \"Override the standard malloc interface (i.e. define entry points for 'malloc', 'free', etc)\" ON)\noption(MI_XMALLOC           \"Enable abort() call on memory allocation failure by default\" OFF)\noption(MI_SHOW_ERRORS       \"Show error and warning messages by default (only enabled by default in DEBUG mode)\" OFF)\noption(MI_GUARDED           \"Build with guard pages behind certain object allocations (implies MI_NO_PADDING=ON)\" OFF)\noption(MI_USE_CXX           \"Use the C++ compiler to compile the library (instead of the C compiler)\" OFF)\noption(MI_OPT_ARCH          \"Only for optimized builds: turn on architecture specific optimizations (for arm64: '-march=armv8.1-a' (2016))\" OFF)\noption(MI_SEE_ASM           \"Generate assembly files\" OFF)\noption(MI_OSX_INTERPOSE     \"Use interpose to override standard malloc on macOS\" ON)\noption(MI_OSX_ZONE          \"Use malloc zone to override standard malloc on macOS\" ON)\noption(MI_WIN_REDIRECT      \"Use redirection module ('mimalloc-redirect') on Windows if compiling mimalloc as a DLL\" ON)\noption(MI_WIN_USE_FIXED_TLS \"Use a fixed TLS slot on Windows to avoid extra tests in the malloc fast path\" OFF)\noption(MI_LOCAL_DYNAMIC_TLS \"Use local-dynamic-tls, a slightly slower but dlopen-compatible thread local storage mechanism (Unix)\" OFF)\noption(MI_LIBC_MUSL         \"Enable this when linking with musl libc\" OFF)\n\noption(MI_DEBUG             \"Enable assertion checks (enabled by default in a debug build)\" OFF)\noption(MI_DEBUG_INTERNAL    \"Enable assertion and internal invariant checks (enabled by default in a debug build)\" OFF)\noption(MI_DEBUG_FULL        \"Enable assertion checks and expensive internal heap invariant checking\" OFF)\n\noption(MI_DEBUG_TSAN        \"Build with thread sanitizer (needs clang)\" OFF)\noption(MI_DEBUG_UBSAN       \"Build with undefined-behavior sanitizer (needs clang++)\" OFF)\noption(MI_TRACK_VALGRIND    \"Compile with Valgrind support (adds a small overhead)\" OFF)\noption(MI_TRACK_ASAN        \"Compile with address sanitizer support (adds a small overhead)\" OFF)\noption(MI_TRACK_ETW         \"Compile with Windows event tracing (ETW) support (adds a small overhead)\" OFF)\n\noption(MI_BUILD_SHARED      \"Build shared library\" ON)\noption(MI_BUILD_STATIC      \"Build static library\" ON)\noption(MI_BUILD_OBJECT      \"Build object library\" ON)\noption(MI_BUILD_TESTS       \"Build test executables\" ON)\n\noption(MI_SKIP_COLLECT_ON_EXIT \"Skip collecting memory on program exit\" OFF)\noption(MI_NO_PADDING        \"Force no use of padding even in DEBUG mode etc.\" OFF)\noption(MI_INSTALL_TOPLEVEL  \"Install directly into $CMAKE_INSTALL_PREFIX instead of PREFIX/lib/mimalloc-version\" OFF)\noption(MI_NO_THP            \"Disable transparent huge pages support on Linux/Android for the mimalloc process only\" OFF)\noption(MI_EXTRA_CPPDEFS     \"Extra pre-processor definitions (use as `-DMI_EXTRA_CPPDEFS=\\\"opt1=val1;opt2=val2\\\"`)\" \"\")\n\n# negated options for vcpkg features\noption(MI_NO_USE_CXX        \"Use plain C compilation (has priority over MI_USE_CXX)\" OFF)\noption(MI_NO_OPT_ARCH       \"Do not use architecture specific optimizations (like '-march=armv8.1-a' for example) (has priority over MI_OPT_ARCH)\" OFF)\n\n# deprecated options\noption(MI_WIN_USE_FLS       \"Use Fiber local storage on Windows to detect thread termination (deprecated)\" OFF)\noption(MI_CHECK_FULL        \"Use full internal invariant checking in DEBUG mode (deprecated, use MI_DEBUG_FULL instead)\" OFF)\noption(MI_USE_LIBATOMIC     \"Explicitly link with -latomic (on older systems) (deprecated and detected automatically)\" OFF)\n\ninclude(CheckLinkerFlag)    # requires cmake 3.18\ninclude(CheckIncludeFiles)\ninclude(GNUInstallDirs)\ninclude(\"cmake/mimalloc-config-version.cmake\")\n\nset(mi_sources\n    src/alloc.c\n    src/alloc-aligned.c\n    src/alloc-posix.c\n    src/arena.c\n    src/bitmap.c\n    src/heap.c\n    src/init.c\n    src/libc.c\n    src/options.c\n    src/os.c\n    src/page.c\n    src/random.c\n    src/segment.c\n    src/segment-map.c\n    src/stats.c\n    src/prim/prim.c)\n\nset(mi_cflags \"\")\nset(mi_cflags_static \"\")            # extra flags for a static library build\nset(mi_cflags_dynamic \"\")           # extra flags for a shared-object library build\nset(mi_libraries \"\")\n\nif(MI_EXTRA_CPPDEFS)\n set(mi_defines ${MI_EXTRA_CPPDEFS})\nelse()\n set(mi_defines \"\")\nendif()\n\n# pass git revision as a define\nif(EXISTS \"${CMAKE_SOURCE_DIR}/.git/index\")\n  find_package(Git)\n  if(GIT_FOUND)\n    execute_process(COMMAND ${GIT_EXECUTABLE} \"describe\" OUTPUT_VARIABLE mi_git_describe RESULT_VARIABLE mi_git_res ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)\n    if(mi_git_res EQUAL \"0\")\n      list(APPEND mi_defines \"MI_GIT_DESCRIBE=${mi_git_describe}\")\n      # add to dependencies so we rebuild if the git head commit changes\n      set_property(GLOBAL APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS \"${CMAKE_SOURCE_DIR}/.git/index\")\n    endif()\n  endif()\nendif()\n\n# -----------------------------------------------------------------------------\n# Convenience: set default build type and compiler depending on the build directory\n# -----------------------------------------------------------------------------\n\nmessage(STATUS \"\")\nif (NOT CMAKE_BUILD_TYPE)\n  if (\"${CMAKE_BINARY_DIR}\" MATCHES \".*((D|d)ebug|asan|tsan|ubsan|valgrind)$\")\n    message(STATUS \"No build type selected, default to 'Debug'\")\n    set(CMAKE_BUILD_TYPE \"Debug\")\n  else()\n    message(STATUS \"No build type selected, default to 'Release'\")\n    set(CMAKE_BUILD_TYPE \"Release\")\n  endif()\nendif()\n\nif (CMAKE_GENERATOR MATCHES \"^Visual Studio.*$\")\n  message(STATUS \"Note: when building with Visual Studio the build type is specified when building.\")\n  message(STATUS \"For example: 'cmake --build . --config=Release\")\nendif()\n\nif(\"${CMAKE_BINARY_DIR}\" MATCHES \".*(S|s)ecure$\")\n  message(STATUS \"Default to secure build\")\n  set(MI_SECURE \"ON\")\nendif()\n\n\n# Determine architecture\nset(MI_OPT_ARCH_FLAGS \"\")\nset(MI_ARCH \"unknown\")\nif(CMAKE_SYSTEM_PROCESSOR MATCHES \"^(x86|i[3456]86)$\" OR CMAKE_GENERATOR_PLATFORM MATCHES \"^(x86|Win32)$\")\n  set(MI_ARCH \"x86\")\nelseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"^(x86_64|x64|amd64|AMD64)$\" OR CMAKE_GENERATOR_PLATFORM STREQUAL \"x64\" OR \"x86_64\" IN_LIST CMAKE_OSX_ARCHITECTURES) # must be before arm64\n  set(MI_ARCH \"x64\")\nelseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"^(aarch64|arm64|armv[89].?|ARM64)$\" OR CMAKE_GENERATOR_PLATFORM STREQUAL \"ARM64\" OR \"arm64\" IN_LIST CMAKE_OSX_ARCHITECTURES)\n  set(MI_ARCH \"arm64\")\nelseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"^(arm|armv[34567].?|ARM)$\")\n  set(MI_ARCH \"arm32\")\nelseif(CMAKE_SYSTEM_PROCESSOR MATCHES \"^(riscv|riscv32|riscv64)$\")\n  if(CMAKE_SIZEOF_VOID_P==4)\n    set(MI_ARCH \"riscv32\")\n  else()\n    set(MI_ARCH \"riscv64\")\n  endif()\nelse()\n  set(MI_ARCH ${CMAKE_SYSTEM_PROCESSOR})\nendif()\nmessage(STATUS \"Architecture: ${MI_ARCH}\") # (${CMAKE_SYSTEM_PROCESSOR}, ${CMAKE_GENERATOR_PLATFORM}, ${CMAKE_GENERATOR})\")\n\n# negative overrides (mainly to support vcpkg features)\nif(MI_NO_USE_CXX)\n  set(MI_USE_CXX \"OFF\")\nendif()\nif(MI_NO_OPT_ARCH)\n  set(MI_OPT_ARCH \"OFF\")\nelseif(MI_ARCH STREQUAL \"arm64\")\n  set(MI_OPT_ARCH \"ON\")  # enable armv8.1-a by default on arm64 unless MI_NO_OPT_ARCH is set\nendif()\n\n\n# -----------------------------------------------------------------------------\n# Process options\n# -----------------------------------------------------------------------------\n\nif(CMAKE_C_COMPILER_ID STREQUAL \"Clang\" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL \"MSVC\")\n  set(MI_CLANG_CL \"ON\")\nendif()\n\n# put -Wall early so other warnings can be disabled selectively\nif(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang\")\n  if (MI_CLANG_CL)\n    list(APPEND mi_cflags -W)\n  else()\n    list(APPEND mi_cflags -Wall -Wextra -Wpedantic)\n  endif()\nendif()\nif(CMAKE_C_COMPILER_ID MATCHES \"GNU\")\n    list(APPEND mi_cflags -Wall -Wextra)\nendif()\nif(CMAKE_C_COMPILER_ID MATCHES \"Intel\")\n    list(APPEND mi_cflags -Wall)\nendif()\n\n# force C++ compilation with msvc or clang-cl to use modern C++ atomics\nif(CMAKE_C_COMPILER_ID MATCHES \"MSVC|Intel\" OR MI_CLANG_CL)\n  set(MI_USE_CXX \"ON\")\nendif()\n\nif(MI_OVERRIDE)\n  message(STATUS \"Override standard malloc (MI_OVERRIDE=ON)\")\n  if(APPLE)\n    if(MI_OSX_ZONE)\n      # use zone's on macOS\n      message(STATUS \"  Use malloc zone to override malloc (MI_OSX_ZONE=ON)\")\n      list(APPEND mi_sources src/prim/osx/alloc-override-zone.c)\n      list(APPEND mi_defines MI_OSX_ZONE=1)\n      if (NOT MI_OSX_INTERPOSE)\n        message(STATUS \"  WARNING: zone overriding usually also needs interpose (use -DMI_OSX_INTERPOSE=ON)\")\n      endif()\n    endif()\n    if(MI_OSX_INTERPOSE)\n      # use interpose on macOS\n      message(STATUS \"  Use interpose to override malloc (MI_OSX_INTERPOSE=ON)\")\n      list(APPEND mi_defines MI_OSX_INTERPOSE=1)\n      if (NOT MI_OSX_ZONE)\n        message(STATUS \"  WARNING: interpose usually also needs zone overriding (use -DMI_OSX_ZONE=ON)\")\n      endif()\n    endif()\n    if(MI_USE_CXX AND MI_OSX_INTERPOSE)\n      message(STATUS \"  WARNING: if dynamically overriding malloc/free, it is more reliable to build mimalloc as C code (use -DMI_USE_CXX=OFF)\")\n    endif()\n  endif()\nendif()\n\nif(WIN32)\n  if (NOT MI_WIN_REDIRECT)\n    # use a negative define for backward compatibility\n    list(APPEND mi_defines MI_WIN_NOREDIRECT=1)\n  endif()\nendif()\n\nif(MI_SECURE)\n  message(STATUS \"Set full secure build (MI_SECURE=ON)\")\n  list(APPEND mi_defines MI_SECURE=4)\nendif()\n\nif(MI_TRACK_VALGRIND)\n  CHECK_INCLUDE_FILES(\"valgrind/valgrind.h;valgrind/memcheck.h\" MI_HAS_VALGRINDH)\n  if (NOT MI_HAS_VALGRINDH)\n    set(MI_TRACK_VALGRIND OFF)\n    message(WARNING \"Cannot find the 'valgrind/valgrind.h' and 'valgrind/memcheck.h' -- install valgrind first?\")\n    message(STATUS  \"Disabling Valgrind support (MI_TRACK_VALGRIND=OFF)\")\n  else()\n    message(STATUS \"Compile with Valgrind support (MI_TRACK_VALGRIND=ON)\")\n    list(APPEND mi_defines MI_TRACK_VALGRIND=1)\n  endif()\nendif()\n\nif(MI_TRACK_ASAN)\n  if (APPLE AND MI_OVERRIDE)\n    set(MI_TRACK_ASAN OFF)\n    message(WARNING \"Cannot enable address sanitizer support on macOS if MI_OVERRIDE is ON (MI_TRACK_ASAN=OFF)\")\n  endif()\n  if (MI_TRACK_VALGRIND)\n    set(MI_TRACK_ASAN OFF)\n    message(WARNING \"Cannot enable address sanitizer support with also Valgrind support enabled (MI_TRACK_ASAN=OFF)\")\n  endif()\n  if(MI_TRACK_ASAN)\n    CHECK_INCLUDE_FILES(\"sanitizer/asan_interface.h\" MI_HAS_ASANH)\n    if (NOT MI_HAS_ASANH)\n      set(MI_TRACK_ASAN OFF)\n      message(WARNING \"Cannot find the 'sanitizer/asan_interface.h' -- install address sanitizer support first\")\n      message(STATUS  \"Compile **without** address sanitizer support (MI_TRACK_ASAN=OFF)\")\n    else()\n      message(STATUS \"Compile with address sanitizer support (MI_TRACK_ASAN=ON)\")\n      list(APPEND mi_defines MI_TRACK_ASAN=1)\n      list(APPEND mi_cflags -fsanitize=address)\n      list(APPEND mi_libraries -fsanitize=address)\n    endif()\n  endif()\nendif()\n\nif(MI_TRACK_ETW)\n  if(NOT WIN32)\n    set(MI_TRACK_ETW OFF)\n    message(WARNING \"Can only enable ETW support on Windows (MI_TRACK_ETW=OFF)\")\n  endif()\n  if (MI_TRACK_VALGRIND OR MI_TRACK_ASAN)\n    set(MI_TRACK_ETW OFF)\n    message(WARNING \"Cannot enable ETW support with also Valgrind or ASAN support enabled (MI_TRACK_ETW=OFF)\")\n  endif()\n  if(MI_TRACK_ETW)\n    message(STATUS \"Compile with Windows event tracing support (MI_TRACK_ETW=ON)\")\n    list(APPEND mi_defines MI_TRACK_ETW=1)\n  endif()\nendif()\n\nif(MI_GUARDED)\n  message(STATUS \"Compile guard pages behind certain object allocations (MI_GUARDED=ON)\")\n  list(APPEND mi_defines MI_GUARDED=1)\n  if(NOT MI_NO_PADDING)\n    message(STATUS \"  Disabling padding due to guard pages (MI_NO_PADDING=ON)\")\n    set(MI_NO_PADDING ON)\n  endif()\nendif()\n\nif(MI_SEE_ASM)\n  message(STATUS \"Generate assembly listings (MI_SEE_ASM=ON)\")\n  list(APPEND mi_cflags -save-temps)\n  if(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang\")\n    message(STATUS \"No GNU Line marker\")\n    list(APPEND mi_cflags -Wno-gnu-line-marker)\n  endif()\nendif()\n\nif(MI_CHECK_FULL)\n  message(STATUS \"The MI_CHECK_FULL option is deprecated, use MI_DEBUG_FULL instead\")\n  set(MI_DEBUG_FULL \"ON\")\nendif()\n\nif (MI_SKIP_COLLECT_ON_EXIT)\n  message(STATUS \"Skip collecting memory on program exit (MI_SKIP_COLLECT_ON_EXIT=ON)\")\n  list(APPEND mi_defines MI_SKIP_COLLECT_ON_EXIT=1)\nendif()\n\nif(MI_DEBUG_FULL)\n  message(STATUS \"Set debug level to full assertion and internal invariant checking (MI_DEBUG_FULL=ON, expensive)\")\n  list(APPEND mi_defines MI_DEBUG=3)   # full invariant checking (mi_assert, mi_assert_internal, and mi_assert_expensive)\nelseif(MI_DEBUG_INTERNAL)\n  message(STATUS \"Set debug level to internal assertion and invariant checking (MI_DEBUG_INTERNAL=ON)\")\n  list(APPEND mi_defines MI_DEBUG=2)   # invariant checking (mi_assert and mi_assert_internal)\nelseif(MI_DEBUG)\n  message(STATUS \"Set debug level to assertion checking (MI_DEBUG=ON)\")\n  list(APPEND mi_defines MI_DEBUG=1)   # assertion checking (mi_assert)\nelseif(CMAKE_BUILD_TYPE MATCHES \"Debug\")\n  message(STATUS \"Set debug level to internal assertion and invariant checking (CMAKE_BUILD_TYPE=Debug)\")\n  set(MI_DEBUG_INTERNAL ON)\n  list(APPEND mi_defines MI_DEBUG=2)   # invariant checking (mi_assert and mi_assert_internal)\nendif()\n\nif(MI_NO_PADDING)\n  message(STATUS \"Suppress any padding of heap blocks (MI_NO_PADDING=ON)\")\n  list(APPEND mi_defines MI_PADDING=0)\nelse()\n  if(MI_PADDING)\n    message(STATUS \"Enable explicit padding of heap blocks (MI_PADDING=ON)\")\n    list(APPEND mi_defines MI_PADDING=1)\n  endif()\nendif()\n\nif(MI_XMALLOC)\n  message(STATUS \"Enable abort() calls on memory allocation failure (MI_XMALLOC=ON)\")\n  list(APPEND mi_defines MI_XMALLOC=1)\nendif()\n\nif(MI_SHOW_ERRORS)\n  message(STATUS \"Enable printing of error and warning messages by default (MI_SHOW_ERRORS=ON)\")\n  list(APPEND mi_defines MI_SHOW_ERRORS=1)\nendif()\n\nif(MI_DEBUG_TSAN)\n  if(CMAKE_C_COMPILER_ID MATCHES \"Clang\")\n    message(STATUS \"Build with thread sanitizer (MI_DEBUG_TSAN=ON)\")\n    list(APPEND mi_defines MI_TSAN=1)\n    list(APPEND mi_cflags -fsanitize=thread -g -O1)\n    list(APPEND mi_libraries -fsanitize=thread)\n  else()\n    message(WARNING \"Can only use thread sanitizer with clang (MI_DEBUG_TSAN=ON but ignored)\")\n  endif()\nendif()\n\nif(MI_DEBUG_UBSAN)\n  if(CMAKE_BUILD_TYPE MATCHES \"Debug\")\n    if(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n      message(STATUS \"Build with undefined-behavior sanitizer (MI_DEBUG_UBSAN=ON)\")\n      list(APPEND mi_defines MI_UBSAN=1)\n      list(APPEND mi_cflags -fsanitize=undefined -g -fno-sanitize-recover=undefined)\n      list(APPEND mi_libraries -fsanitize=undefined)\n      if (NOT MI_USE_CXX)\n        message(STATUS \"(switch to use C++ due to MI_DEBUG_UBSAN)\")\n        set(MI_USE_CXX \"ON\")\n      endif()\n    else()\n      message(WARNING \"Can only use undefined-behavior sanitizer with clang++ (MI_DEBUG_UBSAN=ON but ignored)\")\n    endif()\n  else()\n    message(WARNING \"Can only use undefined-behavior sanitizer with a debug build (CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})\")\n  endif()\nendif()\n\nif(MI_USE_CXX)\n  message(STATUS \"Use the C++ compiler to compile (MI_USE_CXX=ON)\")\n  set_source_files_properties(${mi_sources} PROPERTIES LANGUAGE CXX )\n  set_source_files_properties(src/static.c test/test-api.c test/test-api-fill test/test-stress PROPERTIES LANGUAGE CXX )\n  if(CMAKE_CXX_COMPILER_ID MATCHES \"AppleClang|Clang\")\n    list(APPEND mi_cflags -Wno-deprecated)\n  endif()\n  if(CMAKE_CXX_COMPILER_ID MATCHES \"Intel\" AND NOT CMAKE_CXX_COMPILER_ID MATCHES \"IntelLLVM\")\n    list(APPEND mi_cflags -Kc++)\n  endif()\nendif()\n\nif(CMAKE_SYSTEM_NAME MATCHES \"Linux|Android\")\n  if(MI_NO_THP)\n    message(STATUS \"Disable transparent huge pages support (MI_NO_THP=ON)\")\n    list(APPEND mi_defines MI_NO_THP=1)\n  endif()\nendif()\n\nif(MI_LIBC_MUSL)\n  message(STATUS \"Assume using musl libc (MI_LIBC_MUSL=ON)\")\n  list(APPEND mi_defines MI_LIBC_MUSL=1)\nendif()\n\nif(MI_WIN_USE_FLS)\n  message(STATUS \"Use the Fiber API to detect thread termination (deprecated) (MI_WIN_USE_FLS=ON)\")\n  list(APPEND mi_defines MI_WIN_USE_FLS=1)\nendif()\n\nif(MI_WIN_USE_FIXED_TLS)\n  message(STATUS \"Use fixed TLS slot on Windows to avoid extra tests in the malloc fast path (MI_WIN_USE_FIXED_TLS=ON)\")\n  list(APPEND mi_defines MI_WIN_USE_FIXED_TLS=1)\nendif()\n\n# Check /proc/cpuinfo for an SV39 MMU and limit the virtual address bits.\n# (this will skip the aligned hinting in that case. Issue #939, #949)\nif (EXISTS /proc/cpuinfo)\n  file(STRINGS /proc/cpuinfo mi_sv39_mmu REGEX \"^mmu[ \\t]+:[ \\t]+sv39$\")\n  if (mi_sv39_mmu)\n    MESSAGE( STATUS \"Set virtual address bits to 39 (SV39 MMU detected)\" )\n    list(APPEND mi_defines MI_DEFAULT_VIRTUAL_ADDRESS_BITS=39)\n  endif()\nendif()\n\n# On Haiku use `-DCMAKE_INSTALL_PREFIX` instead, issue #788\n# if(CMAKE_SYSTEM_NAME MATCHES \"Haiku\")\n#   SET(CMAKE_INSTALL_LIBDIR ~/config/non-packaged/lib)\n#   SET(CMAKE_INSTALL_INCLUDEDIR ~/config/non-packaged/headers)\n# endif()\n\n# Compiler flags\nif(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang|GNU\" AND NOT MI_CLANG_CL)\n  list(APPEND mi_cflags -Wno-unknown-pragmas -fvisibility=hidden)\n  if(NOT MI_USE_CXX)\n    list(APPEND mi_cflags -Wstrict-prototypes)\n  endif()\n  if(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang\")\n    list(APPEND mi_cflags -Wno-static-in-inline)\n  endif()\nendif()\n\nif(CMAKE_C_COMPILER_ID MATCHES \"Intel\")\n  list(APPEND mi_cflags -fvisibility=hidden)\nendif()\n\nif(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang|GNU|Intel\" AND NOT CMAKE_SYSTEM_NAME MATCHES \"Haiku\" AND NOT MI_CLANG_CL)\n  if(MI_LOCAL_DYNAMIC_TLS)\n    list(APPEND mi_cflags -ftls-model=local-dynamic)\n  else()\n    if(MI_LIBC_MUSL)\n      # with musl we use local-dynamic for the static build, see issue #644\n      list(APPEND mi_cflags_static  -ftls-model=local-dynamic)\n      list(APPEND mi_cflags_dynamic -ftls-model=initial-exec)\n      message(STATUS \"Use local dynamic TLS for the static build (since MI_LIBC_MUSL=ON)\")\n    else()\n      list(APPEND mi_cflags -ftls-model=initial-exec)\n    endif()\n  endif()\n  if(MI_OVERRIDE)\n    list(APPEND mi_cflags -fno-builtin-malloc)\n  endif()\nendif()\n\nif(CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang|GNU|Intel\" AND NOT CMAKE_SYSTEM_NAME MATCHES \"Haiku\")\n  if(MI_OPT_ARCH)\n    if(APPLE AND CMAKE_C_COMPILER_ID MATCHES \"AppleClang|Clang\" AND CMAKE_OSX_ARCHITECTURES)   # to support multi-arch binaries (#999)\n      if(\"arm64\" IN_LIST CMAKE_OSX_ARCHITECTURES)\n        list(APPEND MI_OPT_ARCH_FLAGS \"-Xarch_arm64;-march=armv8.1-a\")\n      endif()\n    elseif(MI_ARCH STREQUAL \"arm64\")\n      set(MI_OPT_ARCH_FLAGS \"-march=armv8.1-a\")  # fast atomics\n    endif()\n  endif()\nendif()\n\nif (MSVC AND MSVC_VERSION GREATER_EQUAL 1914)\n  list(APPEND mi_cflags /Zc:__cplusplus)\n  if(MI_OPT_ARCH AND NOT MI_CLANG_CL)\n    if(MI_ARCH STREQUAL \"arm64\")\n      set(MI_OPT_ARCH_FLAGS \"/arch:armv8.1\")           # fast atomics\n    endif()\n  endif()\nendif()\n\nif(MINGW)\n  add_definitions(-D_WIN32_WINNT=0x600)                # issue #976\nendif()\n\nif(MI_OPT_ARCH_FLAGS)\n  list(APPEND mi_cflags ${MI_OPT_ARCH_FLAGS})\n  message(STATUS \"Architecture specific optimization is enabled (with ${MI_OPT_ARCH_FLAGS}) (MI_OPT_ARCH=ON)\")\nendif()\n\n# extra needed libraries\n\n# we prefer -l<lib> test over `find_library` as sometimes core libraries\n# like `libatomic` are not on the system path (see issue #898)\nfunction(find_link_library libname outlibname)\n  check_linker_flag(C \"-l${libname}\" mi_has_lib${libname})\n  if (mi_has_lib${libname})\n    message(VERBOSE \"link library: -l${libname}\")\n    set(${outlibname} ${libname} PARENT_SCOPE)\n  else()\n    find_library(MI_LIBPATH_${libname} ${libname})\n    if (MI_LIBPATH_${libname})\n      message(VERBOSE \"link library ${libname} at ${MI_LIBPATH_${libname}}\")\n      set(${outlibname} ${MI_LIBPATH_${libname}} PARENT_SCOPE)\n    else()\n      message(VERBOSE \"link library not found: ${libname}\")\n      set(${outlibname} \"\" PARENT_SCOPE)\n    endif()\n  endif()\nendfunction()\n\nif(WIN32)\n  list(APPEND mi_libraries psapi shell32 user32 advapi32 bcrypt)\nelse()\n  find_link_library(\"pthread\" MI_LIB_PTHREAD)\n  if(MI_LIB_PTHREAD)\n    list(APPEND mi_libraries \"${MI_LIB_PTHREAD}\")\n  endif()\n  find_link_library(\"rt\" MI_LIB_RT)\n  if(MI_LIB_RT)\n    list(APPEND mi_libraries \"${MI_LIB_RT}\")\n  endif()\n  find_link_library(\"atomic\" MI_LIB_ATOMIC)\n  if(MI_LIB_ATOMIC)\n    list(APPEND mi_libraries \"${MI_LIB_ATOMIC}\")\n  endif()\nendif()\n\n\n# -----------------------------------------------------------------------------\n# Install and output names\n# -----------------------------------------------------------------------------\n\n# dynamic/shared library and symlinks always go to /usr/local/lib equivalent\n# we use ${CMAKE_INSTALL_BINDIR} and ${CMAKE_INSTALL_LIBDIR}.\n\n# static libraries and object files, includes, and cmake config files\n# are either installed at top level, or use versioned directories for side-by-side installation (default)\nif (MI_INSTALL_TOPLEVEL)\n  set(mi_install_objdir     \"${CMAKE_INSTALL_LIBDIR}\")\n  set(mi_install_incdir     \"${CMAKE_INSTALL_INCLUDEDIR}\")\n  set(mi_install_cmakedir   \"${CMAKE_INSTALL_LIBDIR}/cmake/mimalloc\")\nelse()\n  set(mi_install_objdir     \"${CMAKE_INSTALL_LIBDIR}/mimalloc-${mi_version}\")       # for static library and object files\n  set(mi_install_incdir     \"${CMAKE_INSTALL_INCLUDEDIR}/mimalloc-${mi_version}\")   # for includes\n  set(mi_install_cmakedir   \"${CMAKE_INSTALL_LIBDIR}/cmake/mimalloc-${mi_version}\") # for cmake package info\nendif()\n\nset(mi_libname \"mimalloc\")\nif(MI_SECURE)\n  set(mi_libname \"${mi_libname}-secure\")\nendif()\nif(MI_TRACK_VALGRIND)\n  set(mi_libname \"${mi_libname}-valgrind\")\nendif()\nif(MI_TRACK_ASAN)\n  set(mi_libname \"${mi_libname}-asan\")\nendif()\nstring(TOLOWER \"${CMAKE_BUILD_TYPE}\" CMAKE_BUILD_TYPE_LC)\nlist(APPEND mi_defines \"MI_CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_LC}\")  #todo: multi-config project needs $<CONFIG> ?\nif(CMAKE_BUILD_TYPE_LC MATCHES \"^(release|relwithdebinfo|minsizerel|none)$\")\n  list(APPEND mi_defines MI_BUILD_RELEASE)\nelse()\n  set(mi_libname \"${mi_libname}-${CMAKE_BUILD_TYPE_LC}\") #append build type (e.g. -debug) if not a release version\nendif()\n\nif(MI_BUILD_SHARED)\n  list(APPEND mi_build_targets \"shared\")\nendif()\nif(MI_BUILD_STATIC)\n  list(APPEND mi_build_targets \"static\")\nendif()\nif(MI_BUILD_OBJECT)\n  list(APPEND mi_build_targets \"object\")\nendif()\nif(MI_BUILD_TESTS)\n  list(APPEND mi_build_targets \"tests\")\nendif()\n\nmessage(STATUS \"\")\nmessage(STATUS \"Library name     : ${mi_libname}\")\nmessage(STATUS \"Version          : ${mi_version}.${mi_version_patch}\")\nmessage(STATUS \"Build type       : ${CMAKE_BUILD_TYPE_LC}\")\nif(MI_USE_CXX)\n  message(STATUS \"C++ Compiler     : ${CMAKE_CXX_COMPILER}\")\nelse()\n  message(STATUS \"C Compiler       : ${CMAKE_C_COMPILER}\")\nendif()\nmessage(STATUS \"Compiler flags   : ${mi_cflags}\")\nmessage(STATUS \"Compiler defines : ${mi_defines}\")\nmessage(STATUS \"Link libraries   : ${mi_libraries}\")\nmessage(STATUS \"Build targets    : ${mi_build_targets}\")\nmessage(STATUS \"\")\n\n# -----------------------------------------------------------------------------\n# Main targets\n# -----------------------------------------------------------------------------\n\n# shared library\nif(MI_BUILD_SHARED)\n  add_library(mimalloc SHARED ${mi_sources})\n  set_target_properties(mimalloc PROPERTIES VERSION ${mi_version} SOVERSION ${mi_version_major} OUTPUT_NAME ${mi_libname} )\n  target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT)\n  target_compile_options(mimalloc PRIVATE ${mi_cflags} ${mi_cflags_dynamic})\n  target_link_libraries(mimalloc PRIVATE ${mi_libraries})\n  target_include_directories(mimalloc PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>\n      $<INSTALL_INTERFACE:${mi_install_incdir}>\n  )\n  install(TARGETS mimalloc EXPORT mimalloc ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})\n  install(EXPORT mimalloc DESTINATION ${mi_install_cmakedir})\n\n  if(WIN32 AND NOT MINGW)\n    # On windows, the import library name for the dll would clash with the static mimalloc.lib library\n    # so we postfix the dll import library with `.dll.lib` (and also the .pdb debug file)\n    set_property(TARGET mimalloc PROPERTY ARCHIVE_OUTPUT_NAME \"${mi_libname}.dll\" )\n    install(FILES \"$<TARGET_FILE_DIR:mimalloc>/${mi_libname}.dll.lib\" DESTINATION ${CMAKE_INSTALL_LIBDIR})\n    set_property(TARGET mimalloc PROPERTY PDB_NAME \"${mi_libname}.dll\")\n    # don't try to install the pdb since it may not be generated depending on the configuration\n    # install(FILES \"$<TARGET_FILE_DIR:mimalloc>/${mi_libname}.dll.pdb\" DESTINATION ${CMAKE_INSTALL_LIBDIR})\n  endif()\n  if(WIN32 AND MI_WIN_REDIRECT)\n    if(MINGW)\n      set_property(TARGET mimalloc PROPERTY PREFIX \"\")\n    endif()\n    # On windows, link and copy the mimalloc redirection dll too.\n    if(CMAKE_GENERATOR_PLATFORM STREQUAL \"arm64ec\")\n      set(MIMALLOC_REDIRECT_SUFFIX \"-arm64ec\")\n    elseif(MI_ARCH STREQUAL \"x64\")\n      set(MIMALLOC_REDIRECT_SUFFIX \"\")\n      if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"ARM64\")\n        message(STATUS \"Note: x64 code emulated on Windows for arm64 should use an arm64ec build of 'mimalloc.dll'\")\n        message(STATUS \"      together with 'mimalloc-redirect-arm64ec.dll'. See the 'bin\\\\readme.md' for more information.\")\n      endif()\n    elseif(MI_ARCH STREQUAL \"x86\")\n      set(MIMALLOC_REDIRECT_SUFFIX \"32\")\n    else()\n      set(MIMALLOC_REDIRECT_SUFFIX \"-${MI_ARCH}\")  # -arm64 etc.\n    endif()\n\n    target_link_libraries(mimalloc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.lib)  # the DLL import library\n    add_custom_command(TARGET mimalloc POST_BUILD\n      COMMAND \"${CMAKE_COMMAND}\" -E copy \"${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll\" $<TARGET_FILE_DIR:mimalloc>\n      COMMENT \"Copy mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll to output directory\")\n    install(FILES \"$<TARGET_FILE_DIR:mimalloc>/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll\" DESTINATION ${CMAKE_INSTALL_BINDIR})\n  endif()\nendif()\n\n\n# static library\nif (MI_BUILD_STATIC)\n  add_library(mimalloc-static STATIC ${mi_sources})\n  set_property(TARGET mimalloc-static PROPERTY OUTPUT_NAME ${mi_libname})\n  set_property(TARGET mimalloc-static PROPERTY POSITION_INDEPENDENT_CODE ON)\n  target_compile_definitions(mimalloc-static PRIVATE ${mi_defines} MI_STATIC_LIB)\n  target_compile_options(mimalloc-static PRIVATE ${mi_cflags} ${mi_cflags_static})\n  target_link_libraries(mimalloc-static PRIVATE ${mi_libraries})\n  target_include_directories(mimalloc-static PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>\n      $<INSTALL_INTERFACE:${mi_install_incdir}>\n  )\n  install(TARGETS mimalloc-static EXPORT mimalloc DESTINATION ${mi_install_objdir} LIBRARY)\n  install(EXPORT mimalloc DESTINATION ${mi_install_cmakedir})\nendif()\n\n# install include files\ninstall(FILES include/mimalloc.h DESTINATION ${mi_install_incdir})\ninstall(FILES include/mimalloc-override.h DESTINATION ${mi_install_incdir})\ninstall(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_incdir})\ninstall(FILES include/mimalloc-stats.h DESTINATION ${mi_install_incdir})\ninstall(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_cmakedir})\ninstall(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_cmakedir})\n\n\n# single object file for more predictable static overriding\nif (MI_BUILD_OBJECT)\n  add_library(mimalloc-obj OBJECT src/static.c)\n  set_property(TARGET mimalloc-obj PROPERTY POSITION_INDEPENDENT_CODE ON)\n  target_compile_definitions(mimalloc-obj PRIVATE ${mi_defines})\n  target_compile_options(mimalloc-obj PRIVATE ${mi_cflags} ${mi_cflags_static})\n  target_include_directories(mimalloc-obj PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>\n      $<INSTALL_INTERFACE:${mi_install_incdir}>\n  )\n\n  # Copy the generated object file (`static.o`) to the output directory (as `mimalloc.o`)\n  if(CMAKE_GENERATOR MATCHES \"^Visual Studio.*$\")\n    set(mimalloc-obj-static \"${CMAKE_CURRENT_BINARY_DIR}/mimalloc-obj.dir/$<CONFIG>/static${CMAKE_C_OUTPUT_EXTENSION}\")\n  else()\n    set(mimalloc-obj-static \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/mimalloc-obj.dir/src/static.c${CMAKE_C_OUTPUT_EXTENSION}\")\n  endif()\n  set(mimalloc-obj-out \"${CMAKE_CURRENT_BINARY_DIR}/${mi_libname}${CMAKE_C_OUTPUT_EXTENSION}\")\n  add_custom_command(OUTPUT ${mimalloc-obj-out} DEPENDS mimalloc-obj COMMAND \"${CMAKE_COMMAND}\" -E copy \"${mimalloc-obj-static}\" \"${mimalloc-obj-out}\")\n  add_custom_target(mimalloc-obj-target ALL DEPENDS ${mimalloc-obj-out})\n\n\n  # the following seems to lead to cmake warnings/errors on some systems, disable for now :-(\n  # install(TARGETS mimalloc-obj EXPORT mimalloc DESTINATION ${mi_install_objdir})\n\n  # the FILES expression can also be: $<TARGET_OBJECTS:mimalloc-obj>\n  # but that fails cmake versions less than 3.10 so we leave it as is for now\n  install(FILES ${mimalloc-obj-static}\n          DESTINATION ${mi_install_objdir}\n          RENAME ${mi_libname}${CMAKE_C_OUTPUT_EXTENSION} )\nendif()\n\n\n# pkg-config file support\nset(mi_pc_libraries \"\")\nforeach(item IN LISTS mi_libraries)\n  if(item MATCHES \" *[-].*\")\n    set(mi_pc_libraries \"${mi_pc_libraries} ${item}\")\n  else()\n    set(mi_pc_libraries \"${mi_pc_libraries} -l${item}\")\n  endif()\nendforeach()\n\ninclude(\"cmake/JoinPaths.cmake\")\njoin_paths(mi_pc_includedir \"\\${prefix}\" \"${CMAKE_INSTALL_INCLUDEDIR}\")\njoin_paths(mi_pc_libdir \"\\${prefix}\" \"${CMAKE_INSTALL_LIBDIR}\")\n\nconfigure_file(mimalloc.pc.in mimalloc.pc @ONLY)\ninstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/mimalloc.pc\"\n        DESTINATION \"${CMAKE_INSTALL_LIBDIR}/pkgconfig/\")\n\n\n\n# -----------------------------------------------------------------------------\n# API surface testing\n# -----------------------------------------------------------------------------\n\nif (MI_BUILD_TESTS)\n  enable_testing()\n\n  # static link tests\n  foreach(TEST_NAME api api-fill stress)\n    add_executable(mimalloc-test-${TEST_NAME} test/test-${TEST_NAME}.c)\n    target_compile_definitions(mimalloc-test-${TEST_NAME} PRIVATE ${mi_defines})\n    target_compile_options(mimalloc-test-${TEST_NAME} PRIVATE ${mi_cflags})\n    target_include_directories(mimalloc-test-${TEST_NAME} PRIVATE include)\n    if(MI_BUILD_STATIC AND NOT MI_DEBUG_TSAN)\n      target_link_libraries(mimalloc-test-${TEST_NAME} PRIVATE mimalloc-static ${mi_libraries})\n    elseif(MI_BUILD_SHARED)\n      target_link_libraries(mimalloc-test-${TEST_NAME} PRIVATE mimalloc ${mi_libraries})\n    else()\n      message(STATUS \"cannot build TSAN tests without MI_BUILD_SHARED being enabled\")\n    endif()\n    add_test(NAME test-${TEST_NAME} COMMAND mimalloc-test-${TEST_NAME})\n  endforeach()\n\n  # dynamic override test\n  if(MI_BUILD_SHARED AND NOT (MI_TRACK_ASAN OR MI_DEBUG_TSAN OR MI_DEBUG_UBSAN))\n    add_executable(mimalloc-test-stress-dynamic test/test-stress.c)\n    target_compile_definitions(mimalloc-test-stress-dynamic PRIVATE ${mi_defines} \"USE_STD_MALLOC=1\")\n    target_compile_options(mimalloc-test-stress-dynamic PRIVATE ${mi_cflags})\n    target_include_directories(mimalloc-test-stress-dynamic PRIVATE include)\n    if(WIN32)\n      target_compile_definitions(mimalloc-test-stress-dynamic PRIVATE \"MI_LINK_VERSION=1\")  # link mi_version\n      target_link_libraries(mimalloc-test-stress-dynamic PRIVATE mimalloc ${mi_libraries})  # link mi_version\n      add_test(NAME test-stress-dynamic COMMAND ${CMAKE_COMMAND} -E env MIMALLOC_VERBOSE=1 $<TARGET_FILE:mimalloc-test-stress-dynamic>)\n    else()\n      target_link_libraries(mimalloc-test-stress-dynamic PRIVATE ${mi_libraries}) # pthreads, issue 1158\n      if(APPLE)\n        set(LD_PRELOAD \"DYLD_INSERT_LIBRARIES\")\n      else()\n        set(LD_PRELOAD \"LD_PRELOAD\")\n      endif()\n      add_test(NAME test-stress-dynamic COMMAND ${CMAKE_COMMAND} -E env MIMALLOC_VERBOSE=1 ${LD_PRELOAD}=$<TARGET_FILE:mimalloc> $<TARGET_FILE:mimalloc-test-stress-dynamic>)\n    endif()\n  endif()\nendif()\n\n# -----------------------------------------------------------------------------\n# Set override properties\n# -----------------------------------------------------------------------------\nif (MI_OVERRIDE)\n  if (MI_BUILD_SHARED)\n    target_compile_definitions(mimalloc PRIVATE MI_MALLOC_OVERRIDE)\n  endif()\n  if(NOT WIN32)\n    # It is only possible to override malloc on Windows when building as a DLL.\n    if (MI_BUILD_STATIC)\n      target_compile_definitions(mimalloc-static PRIVATE MI_MALLOC_OVERRIDE)\n    endif()\n    if (MI_BUILD_OBJECT)\n      target_compile_definitions(mimalloc-obj PRIVATE MI_MALLOC_OVERRIDE)\n    endif()\n  endif()\nendif()\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-2025 Microsoft Corporation, Daan Leijen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->\n\n## Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). \n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "# Starter pipeline\n# Start with a minimal pipeline that you can customize to build and deploy your code.\n# Add steps that build, run tests, deploy, and more:\n# https://aka.ms/yaml\n\ntrigger:\n  branches:\n    include:\n    - main\n    - dev*\n  tags:\n    include:\n    - v*\n\njobs:\n- job:\n  displayName: Windows 2022\n  pool:\n    vmImage:\n      windows-2022\n  strategy:\n    matrix:\n      Debug:\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n        MSBuildConfiguration: Debug\n      Release:\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n        MSBuildConfiguration: Release\n      Secure:\n        BuildType: secure\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON\n        MSBuildConfiguration: Release\n      Debug x86:\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -A Win32\n        MSBuildConfiguration: Debug\n      Release x86:\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -A Win32\n        MSBuildConfiguration: Release\n      Debug Fixed TLS:\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_WIN_USE_FIXED_TLS=ON\n        MSBuildConfiguration: Debug\n      Release Fixed TLS:\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_WIN_USE_FIXED_TLS=ON\n        MSBuildConfiguration: Release\n  steps:\n  - task: CMake@1\n    inputs:\n      workingDirectory: $(BuildType)\n      cmakeArgs: .. $(cmakeExtraArgs)\n  - task: MSBuild@1\n    inputs:\n      solution: $(BuildType)/libmimalloc.sln\n      configuration: '$(MSBuildConfiguration)'\n      msbuildArguments: -m\n  - script: ctest --verbose --timeout 240 -C $(MSBuildConfiguration)\n    workingDirectory: $(BuildType)\n    displayName: CTest\n  #- script: $(BuildType)\\$(BuildType)\\mimalloc-test-stress\n  #  displayName: TestStress\n  #- upload: $(Build.SourcesDirectory)/$(BuildType)\n  #  artifact: mimalloc-windows-$(BuildType)\n\n- job:\n  displayName: Ubuntu 22.04\n  pool:\n    vmImage:\n     ubuntu-22.04\n  strategy:\n    matrix:\n      Debug:\n        CC: gcc\n        CXX: g++\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Release:\n        CC: gcc\n        CXX: g++\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n      Secure:\n        CC: gcc\n        CXX: g++\n        BuildType: secure\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON\n      Debug++:\n        CC: gcc\n        CXX: g++\n        BuildType: debug-cxx\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON\n      Debug Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Release Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: release-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n      Secure Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: secure-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON\n      Debug++ Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-clang-cxx\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON\n      Debug ASAN Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-asan-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_TRACK_ASAN=ON\n      Debug UBSAN Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-ubsan-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_DEBUG_UBSAN=ON\n      Debug TSAN Clang++:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-tsan-clang-cxx\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMI_USE_CXX=ON -DMI_DEBUG_TSAN=ON\n      Debug Guarded Clang:\n        CC: clang\n        CXX: clang\n        BuildType: debug-guarded-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMI_DEBUG_FULL=ON -DMI_GUARDED=ON\n\n  steps:\n  - task: CMake@1\n    inputs:\n      workingDirectory: $(BuildType)\n      cmakeArgs: .. $(cmakeExtraArgs)\n  - script: make -j$(nproc) -C $(BuildType)\n    displayName: Make\n  - script: ctest --verbose --timeout 240\n    workingDirectory: $(BuildType)\n    displayName: CTest\n    env:\n      MIMALLOC_GUARDED_SAMPLE_RATE: 1000\n#  - upload: $(Build.SourcesDirectory)/$(BuildType)\n#    artifact: mimalloc-ubuntu-$(BuildType)\n\n- job:\n  displayName: macOS 14 (Sonoma)\n  pool:\n    vmImage:\n      macOS-14\n  strategy:\n    matrix:\n      Debug:\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Release:\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n      Secure:\n        BuildType: secure\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON\n  steps:\n  - task: CMake@1\n    inputs:\n      workingDirectory: $(BuildType)\n      cmakeArgs: .. $(cmakeExtraArgs)\n  - script: make -j$(sysctl -n hw.ncpu) -C $(BuildType)\n    displayName: Make\n  - script: ctest --verbose --timeout 240\n    workingDirectory: $(BuildType)\n    displayName: CTest\n    \n#  - upload: $(Build.SourcesDirectory)/$(BuildType)\n#    artifact: mimalloc-macos-$(BuildType)\n\n# ----------------------------------------------------------\n# Other OS versions (just debug mode)\n# ----------------------------------------------------------\n\n- job:\n  displayName: Ubuntu 24.04\n  pool:\n    vmImage:\n      ubuntu-24.04\n  strategy:\n    matrix:\n      Debug:\n        CC: gcc\n        CXX: g++\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Debug++:\n        CC: gcc\n        CXX: g++\n        BuildType: debug-cxx\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON\n      Debug Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Debug++ Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: debug-clang-cxx\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON\n      Release Clang:\n        CC: clang\n        CXX: clang++\n        BuildType: release-clang\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n  steps:\n  - task: CMake@1\n    inputs:\n      workingDirectory: $(BuildType)\n      cmakeArgs: .. $(cmakeExtraArgs)\n  - script: make -j$(nproc) -C $(BuildType)\n    displayName: Make\n  - script: ctest --verbose --timeout 240\n    workingDirectory: $(BuildType)\n    displayName: CTest\n\n- job:\n  displayName: macOS 15 (Sequoia)\n  pool:\n    vmImage:\n      macOS-15\n  strategy:\n    matrix:\n      Debug:\n        BuildType: debug\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON\n      Release:\n        BuildType: release\n        cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release\n  steps:\n  - task: CMake@1\n    inputs:\n      workingDirectory: $(BuildType)\n      cmakeArgs: .. $(cmakeExtraArgs)\n  - script: make -j$(sysctl -n hw.ncpu) -C $(BuildType)\n    displayName: Make\n  - script: ctest --verbose --timeout 240\n    workingDirectory: $(BuildType)\n    displayName: CTest\n"
  },
  {
    "path": "bin/readme.md",
    "content": "# Windows Override\n\n<span id=\"override_on_windows\">We use a separate redirection DLL to override mimalloc on Windows</span> \nsuch that we redirect all malloc/free calls that go through the (dynamic) C runtime allocator, \nincluding those from other DLL's or libraries. As it intercepts all allocation calls on a low level, \nit can be used on large programs that include other 3rd party components.\nThere are four requirements to make the overriding work well:\n\n1. Use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch).\n\n2. Link your program explicitly with the `mimalloc.dll.lib` export library for\n   the `mimalloc.dll` -- which contains all mimalloc functionality.\n   To ensure the `mimalloc.dll` is actually loaded at run-time it is easiest \n   to insert some call to the mimalloc API in the `main` function, like `mi_version()`\n   (or use the `/include:mi_version` switch on the linker, or\n   similarly, `#pragma comment(linker, \"/include:mi_version\")` in some source file). \n   See the `mimalloc-test-override` project for an example on how to use this. \n\n3. The `mimalloc-redirect.dll` must be put in the same folder as the main \n   `mimalloc.dll` at runtime (as it is a dependency of that DLL).\n   The redirection DLL ensures that all calls to the C runtime malloc API get \n   redirected to mimalloc functions (which reside in `mimalloc.dll`).\n\n4. Ensure the `mimalloc.dll` comes as early as possible in the import\n   list of the final executable (so it can intercept all potential allocations).\n   You can use `minject -l <exe>` to check this if needed.\n\n```csharp\n┌──────────────┐                                                    \n│ Your Program │                                                    \n└────┬─────────┘                                                    \n     │                                                              \n     │ mi_version()  ┌───────────────┐     ┌───────────────────────┐\n     ├──────────────►│ mimalloc.dll  ├────►│ mimalloc-redirect.dll │\n     │               └──────┬────────┘     └───────────────────────┘\n     │                      ▼                                       \n     │ malloc() etc. ┌──────────────┐                               \n     ├──────────────►│ ucrtbase.dll │                               \n     │               └──────────────┘                               \n     │                                                              \n     │                                                              \n     └──────────────► ...                                           \n```\n\nFor best performance on Windows with C++, it\nis also recommended to also override the `new`/`delete` operations (by including\n[`mimalloc-new-delete.h`](../include/mimalloc-new-delete.h) \na single(!) source file in your project).\n\nThe environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic\noverriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully \nredirected.\n\n### Other Platforms\n\nYou always link with `mimalloc.dll` but for different platforms you may \nneed a specific redirection DLL:\n\n- __x64__: `mimalloc-redirect.dll`.\n- __x86__: `mimalloc-redirect32.dll`. Use for older 32-bit Windows programs.\n- __arm64__: `mimalloc-redirect-arm64.dll`. Use for native Windows arm64 programs.\n- __arm64ec__: `mimalloc-redirect-arm64ec.dll`. The [arm64ec] ABI is \"emulation compatible\" \n  mode on Windows arm64. Unfortunately we cannot run x64 code emulated on Windows arm64 with\n  the x64 mimalloc override directly (since the C runtime always uses `arm64ec`). Instead:\n  1. Build the program as normal for x64 and link as normal with the x64 \n     `mimalloc.lib` export library.\n  2. Now separately build `mimalloc.dll` in `arm64ec` mode and _overwrite_ your\n     previous (x64) `mimalloc.dll` -- the loader can handle the mix of arm64ec\n     and x64 code. Now use `mimalloc-redirect-arm64ec.dll` to match your new\n     arm64ec `mimalloc.dll`. The main program stays as is and can be fully x64 \n     or contain more arm64ec modules. At runtime, the arm64ec `mimalloc.dll` will\n     run with native arm64 instructions while the rest of the program runs emulated x64.\n\n[arm64ec]: https://learn.microsoft.com/en-us/windows/arm/arm64ec\n\n\n### Minject\n\nWe cannot always re-link an executable with `mimalloc.dll`, and similarly, we \ncannot always ensure that the DLL comes first in the import table of the final executable.\nIn many cases though we can patch existing executables without any recompilation\nif they are linked with the dynamic C runtime (`ucrtbase.dll`) -- just put the \n`mimalloc.dll` into the import table (and put `mimalloc-redirect.dll` in the same \ndirectory) Such patching can be done for example with [CFF Explorer](https://ntcore.com/?page_id=388).\n\nThe `minject` program can also do this from the command line\nUse `minject --help` for options:\n\n```\n> minject --help\n\nminject:\n  Injects the mimalloc dll into the import table of a 64-bit executable,\n  and/or ensures that it comes first in het import table.\n\nusage:\n  > minject [options] <exe>\n\noptions:\n  -h   --help        show this help\n  -v   --verbose     be verbose\n  -l   --list        only list imported modules\n  -i   --inplace     update the exe in-place (make sure there is a backup!)\n  -f   --force       always overwrite without prompting\n       --postfix=<p> use <p> as a postfix to the mimalloc dll.\n                     e.g. use --postfix=debug to link with mimalloc-debug.dll\n\nnotes:\n  Without '--inplace' an injected <exe> is generated with the same name ending in '-mi'.\n  Ensure 'mimalloc-redirect.dll' is in the same folder as the mimalloc dll.\n\nexamples:\n  > minject --list myprogram.exe\n  > minject --force --inplace myprogram.exe\n```  \n\nFor x86 32-bit binaries, use `minject32`, and for arm64 binaries use `minject-arm64`.\n\n"
  },
  {
    "path": "cmake/JoinPaths.cmake",
    "content": "# This module provides function for joining paths\n# known from most languages\n#\n# SPDX-License-Identifier: (MIT OR CC0-1.0)\n# Copyright 2020 Jan Tojnar\n# https://github.com/jtojnar/cmake-snips\n#\n# Modelled after Python’s os.path.join\n# https://docs.python.org/3.7/library/os.path.html#os.path.join\n# Windows not supported\nfunction(join_paths joined_path first_path_segment)\n    set(temp_path \"${first_path_segment}\")\n    foreach(current_segment IN LISTS ARGN)\n        if(NOT (\"${current_segment}\" STREQUAL \"\"))\n            if(IS_ABSOLUTE \"${current_segment}\")\n                set(temp_path \"${current_segment}\")\n            else()\n                set(temp_path \"${temp_path}/${current_segment}\")\n            endif()\n        endif()\n    endforeach()\n    set(${joined_path} \"${temp_path}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/mimalloc-config-version.cmake",
    "content": "set(mi_version_major 2)\nset(mi_version_minor 2)\nset(mi_version_patch 7)\nset(mi_version ${mi_version_major}.${mi_version_minor})\n\nset(PACKAGE_VERSION ${mi_version})\nif(PACKAGE_FIND_VERSION_MAJOR)\n    if(\"${PACKAGE_FIND_VERSION_MAJOR}\" EQUAL \"${mi_version_major}\")\n        if (\"${PACKAGE_FIND_VERSION_MINOR}\" EQUAL \"${mi_version_minor}\")\n            set(PACKAGE_VERSION_EXACT TRUE)\n        elseif(\"${PACKAGE_FIND_VERSION_MINOR}\" LESS \"${mi_version_minor}\")\n            set(PACKAGE_VERSION_COMPATIBLE TRUE)\n        else()\n            set(PACKAGE_VERSION_UNSUITABLE TRUE)\n        endif()\n    else()\n        set(PACKAGE_VERSION_UNSUITABLE TRUE)\n    endif()\nendif()\n"
  },
  {
    "path": "cmake/mimalloc-config.cmake",
    "content": "include(${CMAKE_CURRENT_LIST_DIR}/mimalloc.cmake)\nget_filename_component(MIMALLOC_CMAKE_DIR \"${CMAKE_CURRENT_LIST_DIR}\" PATH)  # one up from the cmake dir, e.g. /usr/local/lib/cmake/mimalloc-2.0\nget_filename_component(MIMALLOC_VERSION_DIR \"${CMAKE_CURRENT_LIST_DIR}\" NAME)\nstring(REPLACE \"/lib/cmake\" \"/lib\" MIMALLOC_LIBRARY_DIR \"${MIMALLOC_CMAKE_DIR}\")\nif(\"${MIMALLOC_VERSION_DIR}\" EQUAL \"mimalloc\")\n  # top level install\n  string(REPLACE \"/lib/cmake\" \"/include\" MIMALLOC_INCLUDE_DIR \"${MIMALLOC_CMAKE_DIR}\")\n  set(MIMALLOC_OBJECT_DIR \"${MIMALLOC_LIBRARY_DIR}\")\nelse()\n  # versioned\n  string(REPLACE \"/lib/cmake/\" \"/include/\" MIMALLOC_INCLUDE_DIR \"${CMAKE_CURRENT_LIST_DIR}\")\n  string(REPLACE \"/lib/cmake/\" \"/lib/\" MIMALLOC_OBJECT_DIR \"${CMAKE_CURRENT_LIST_DIR}\")\nendif()\nset(MIMALLOC_TARGET_DIR \"${MIMALLOC_LIBRARY_DIR}\") # legacy\n"
  },
  {
    "path": "contrib/docker/alpine/Dockerfile",
    "content": "# alpine image  \nFROM alpine\n\n# Install tools\nRUN apk add build-base make cmake\nRUN apk add git\nRUN apk add vim\n\nRUN mkdir -p  /home/dev\nWORKDIR /home/dev\n\n# Get mimalloc\nRUN git clone https://github.com/microsoft/mimalloc -b dev2\nRUN mkdir -p mimalloc/out/release\nRUN mkdir -p mimalloc/out/debug\n\n# Build mimalloc debug\nWORKDIR /home/dev/mimalloc/out/debug\nRUN cmake ../.. -DMI_DEBUG_FULL=ON\nRUN make -j\nRUN make test\n\nCMD [\"/bin/sh\"]"
  },
  {
    "path": "contrib/docker/alpine-arm32v7/Dockerfile",
    "content": "# install from an image\n# download first an appropriate tar.gz image into the current directory\n# from <https://github.com/alpinelinux/docker-alpine/tree/edge/armv7>\nFROM scratch\n\n# Substitute the image name that was downloaded\nADD alpine-minirootfs-20240329-armv7.tar.gz /\n\n# Install tools\nRUN apk add build-base make cmake\nRUN apk add git\nRUN apk add vim\n\nRUN mkdir -p  /home/dev\nWORKDIR /home/dev\n\n# Get mimalloc\nRUN git clone https://github.com/microsoft/mimalloc -b dev2\nRUN mkdir -p mimalloc/out/release\nRUN mkdir -p mimalloc/out/debug\n\n# Build mimalloc debug\nWORKDIR /home/dev/mimalloc/out/debug\nRUN cmake ../.. -DMI_DEBUG_FULL=ON\nRUN make -j\nRUN make test\n\nCMD [\"/bin/sh\"]\n"
  },
  {
    "path": "contrib/docker/alpine-x86/Dockerfile",
    "content": "# install from an image\n# download first an appropriate tar.gz image into the current directory\n# from <https://github.com/alpinelinux/docker-alpine/tree/edge/x86>\nFROM scratch\n\n# Substitute the image name that was downloaded\nADD alpine-minirootfs-20250108-x86.tar.gz /\n\n# Install tools\nRUN apk add build-base make cmake\nRUN apk add git\nRUN apk add vim\n\nRUN mkdir -p  /home/dev\nWORKDIR /home/dev\n\n# Get mimalloc\nRUN git clone https://github.com/microsoft/mimalloc -b dev2\nRUN mkdir -p mimalloc/out/release\nRUN mkdir -p mimalloc/out/debug\n\n# Build mimalloc debug\nWORKDIR /home/dev/mimalloc/out/debug\nRUN cmake ../.. -DMI_DEBUG_FULL=ON\n# RUN make -j\n# RUN make test\n\nCMD [\"/bin/sh\"]\n"
  },
  {
    "path": "contrib/docker/manylinux-x64/Dockerfile",
    "content": "FROM quay.io/pypa/manylinux2014_x86_64\n\n# Install tools\nRUN yum install -y openssl-devel\nRUN yum install -y gcc gcc-c++ kernel-devel make\nRUN yum install -y git cmake\nRUN yum install -y vim\n\nRUN mkdir -p  /home/dev\nWORKDIR /home/dev\n\n# Get mimalloc\nRUN git clone https://github.com/microsoft/mimalloc -b dev2\nRUN mkdir -p mimalloc/out/release\nRUN mkdir -p mimalloc/out/debug\n\n# Build mimalloc debug\nWORKDIR /home/dev/mimalloc/out/debug\nRUN cmake ../.. -DMI_DEBUG_FULL=ON\nRUN make -j\nRUN make test\n\nCMD [\"/bin/sh\"]"
  },
  {
    "path": "contrib/docker/readme.md",
    "content": "Various example docker files used for testing.\n\nUsage:\n\n```\n> cd <host>\n> docker build -t <host>-mimalloc .\n> docker run -it <host>-mimalloc\n>> make test\n```\n"
  },
  {
    "path": "contrib/nuget/nuget-release-pipeline.yml",
    "content": "# Microsoft.Mimalloc NuGet Release Pipeline\n# Manually triggered pipeline to build, sign, and publish the mimalloc NuGet package.\n# Builds x64 and ARM64 Windows binaries, signs via ESRP, and packs NuGet.\n\ntrigger: none\npr: none\n\nparameters:\n  - name: version\n    displayName: 'NuGet package version'\n    type: string\n    default: '1.0.0'\n\n  - name: buildConfig\n    displayName: 'Build configuration'\n    type: string\n    default: 'Release'\n    values:\n      - Release\n      - Debug\n\n  - name: signBinaries\n    displayName: 'Sign binaries (ESRP)?'\n    type: string\n    default: 'No'\n    values:\n      - 'Yes'\n      - 'No'\n\n  - name: publishToFeed\n    displayName: 'Publish NuGet to artifact feed?'\n    type: boolean\n    default: false\n\nvariables:\n  nuspecPath: '$(Build.SourcesDirectory)/contrib/nuget/Microsoft.Mimalloc.nuspec'\n  artifactStaging: '$(Build.ArtifactStagingDirectory)'\n"
  },
  {
    "path": "contrib/vcpkg/portfile.cmake",
    "content": "vcpkg_from_github(\n  OUT_SOURCE_PATH SOURCE_PATH\n  REPO microsoft/mimalloc\n  HEAD_REF master\n\n  # The \"REF\" can be a commit hash, branch name (dev3), or a version (v3.2.7).\n  REF \"v${VERSION}\"\n  # REF e2db21e9ba9fb9172b7b0aa0fe9b8742525e8774\n\n  # The sha512 is the hash of the tar.gz bundle.\n  # (To get the sha512, run `vcpkg install \"mimalloc[override]\" --overlay-ports=<dir of this file>` and copy the sha from the error message.)\n  SHA512 5830ceb1bf0d02f50fe586caaad87624ba8eba1bb66e68e8201894221cf6f51854f5a9667fc98358c3b430dae6f9bf529bfcb74d42debe6f40a487265053371c\n)\n\nvcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS\n  FEATURES\n    c           MI_NO_USE_CXX\n    guarded     MI_GUARDED\n    secure      MI_SECURE\n    override    MI_OVERRIDE\n    optarch     MI_OPT_ARCH\n    nooptarch   MI_NO_OPT_ARCH\n    optsimd     MI_OPT_SIMD\n    xmalloc     MI_XMALLOC\n    asm         MI_SEE_ASM\n)\nstring(COMPARE EQUAL \"${VCPKG_LIBRARY_LINKAGE}\" \"static\" MI_BUILD_STATIC)\nstring(COMPARE EQUAL \"${VCPKG_LIBRARY_LINKAGE}\" \"dynamic\" MI_BUILD_SHARED)\n\nvcpkg_cmake_configure(\n  SOURCE_PATH \"${SOURCE_PATH}\"\n  OPTIONS\n    -DMI_USE_CXX=ON\n    -DMI_BUILD_TESTS=OFF\n    -DMI_BUILD_OBJECT=ON\n    -DMI_BUILD_STATIC=${MI_BUILD_STATIC}\n    -DMI_BUILD_SHARED=${MI_BUILD_SHARED}\n    -DMI_INSTALL_TOPLEVEL=ON\n    ${FEATURE_OPTIONS}\n)\n\nvcpkg_cmake_install()\nvcpkg_copy_pdbs()\n\nfile(COPY\n  \"${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake\"\n  \"${CMAKE_CURRENT_LIST_DIR}/usage\"\n  DESTINATION \"${CURRENT_PACKAGES_DIR}/share/${PORT}\"\n)\nvcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/mimalloc)\n\nif(VCPKG_LIBRARY_LINKAGE STREQUAL \"dynamic\")\n  # todo: why is this needed?\n  vcpkg_replace_string(\n    \"${CURRENT_PACKAGES_DIR}/include/mimalloc.h\"\n    \"!defined(MI_SHARED_LIB)\"\n    \"0 // !defined(MI_SHARED_LIB)\"\n  )\nendif()\nfile(REMOVE_RECURSE \"${CURRENT_PACKAGES_DIR}/debug/include\")\nfile(REMOVE_RECURSE \"${CURRENT_PACKAGES_DIR}/debug/share\")\n\nvcpkg_fixup_pkgconfig()\nvcpkg_install_copyright(FILE_LIST \"${SOURCE_PATH}/LICENSE\")\n"
  },
  {
    "path": "contrib/vcpkg/readme.md",
    "content": "# Vcpkg support\n\nThis directory is meant to provide the sources for the official [vcpkg port] \nof mimalloc, but can also be used to override the official port with\nyour own variant.\n\nFor example, you can edit the [`portfile.cmake`](portfile.cmake) \nto check out a specific commit, version, or branch of mimalloc, or set further options. \nYou can install such custom port as:\n\n```sh\n$ vcpkg install \"mimalloc[override]\" --recurse --overlay-ports=./contrib/vcpkg\n```\n\nThis will also show the correct sha512 hash if you use a custom version.\nAnother way is to refer to the overlay from the [vcpkg-configuration.json](https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-configuration-json) file.\nSee also the vcpkg [documentation](https://learn.microsoft.com/en-us/vcpkg/produce/update-package-version) for more information.\n\n\n# Using mimalloc from vcpkg\n\nWhen using [cmake with vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started?pivots=shell-powershell), \nyou can use mimalloc from the `CMakeLists.txt` as:\n\n```cmake\nfind_package(mimalloc CONFIG REQUIRED)\ntarget_link_libraries(main PRIVATE mimalloc)\n```\n\nSee [`test/CMakeLists.txt](../../test/CMakeLists.txt) for more examples.\n\n\n# Acknowledgements\n\nThe original port for vckpg was contributed by many people, including: @vicroms, @myd7349, @PhoubeHui, @LilyWangL,\n@JonLiu1993, @RT2Code, Remy Tassoux, @wangao, @BillyONeal, @jiayuehua, @dg0yt, @gerar-ryan-immersaview, @nickdademo, \nand @jimwang118 -- Thank you so much!\n\n\n[vcpkg port]: https://github.com/microsoft/vcpkg/tree/master/ports/mimalloc\n"
  },
  {
    "path": "contrib/vcpkg/usage",
    "content": "Use the following CMake targets to import mimalloc:\n\n  find_package(mimalloc CONFIG REQUIRED)\n  target_link_libraries(main PRIVATE mimalloc)\n\nAnd use mimalloc in your sources as:\n\n  #include <mimalloc.h>\n  #include <stdio.h>\n  int main(int argc, char** argv) {\n    int* p = mi_malloc_tp(int);\n    *p = mi_version();\n    printf(\"mimalloc version: %d\\n\", *p);\n    mi_free(p);\n    return 0;\n  }\n\nWhen dynamically overriding on Windows, ensure `mimalloc.dll` is linked through some call to\nmimalloc (e.g. `mi_version()`), and that the `mimalloc-redirect.dll` is in the same directory.\nSee https://github.com/microsoft/mimalloc/blob/dev/bin/readme.md for detailed information.\n"
  },
  {
    "path": "contrib/vcpkg/vcpkg-cmake-wrapper.cmake",
    "content": "_find_package(${ARGS})\n\nif(CMAKE_CURRENT_LIST_DIR STREQUAL \"${MIMALLOC_CMAKE_DIR}/${MIMALLOC_VERSION_DIR}\")\n    set(MIMALLOC_INCLUDE_DIR \"${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include\")\n    # As in vcpkg.cmake\n    if(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE MATCHES \"^[Dd][Ee][Bb][Uu][Gg]$\")\n        set(MIMALLOC_LIBRARY_DIR \"${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug/lib\")\n    else()\n        set(MIMALLOC_LIBRARY_DIR \"${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib\")\n    endif()\n    set(MIMALLOC_OBJECT_DIR \"${MIMALLOC_LIBRARY_DIR}\")\n    set(MIMALLOC_TARGET_DIR \"${MIMALLOC_LIBRARY_DIR}\")\nendif()\n\n# vcpkg always configures either a static or dynamic library.\n# ensure to always expose the mimalloc target as either the static or dynamic build.\nif(TARGET mimalloc-static AND NOT TARGET mimalloc)\n  add_library(mimalloc INTERFACE IMPORTED)\n  set_target_properties(mimalloc PROPERTIES INTERFACE_LINK_LIBRARIES mimalloc-static)\nendif()\n"
  },
  {
    "path": "contrib/vcpkg/vcpkg.json",
    "content": "{\n  \"name\": \"mimalloc\",\n  \"version\": \"3.2.8\",\n  \"port-version\": 0,\n  \"description\": \"Compact general purpose allocator with excellent performance\",\n  \"homepage\": \"https://github.com/microsoft/mimalloc\",\n  \"license\": \"MIT\",\n  \"supports\": \"!uwp\",\n  \"dependencies\": [\n    {\n      \"name\": \"vcpkg-cmake\",\n      \"host\": true\n    },\n    {\n      \"name\": \"vcpkg-cmake-config\",\n      \"host\": true\n    }\n  ],\n  \"features\": {\n    \"c\": {\n      \"description\": \"Use C11 compilation (this can still override new/delete)\"\n    },\n    \"override\": {\n      \"description\": \"Override the standard malloc/free interface\"\n    },\n    \"secure\": {\n      \"description\": \"Use full security mitigations (like guard pages and randomization)\"\n    },\n    \"guarded\": {\n      \"description\": \"Use build that support guard pages after objects controlled with MIMALLOC_GUARDED_SAMPLE_RATE\"\n    },\n    \"xmalloc\": {\n      \"description\": \"If out-of-memory, call abort() instead of returning NULL\"\n    },\n    \"optarch\": {\n      \"description\": \"Use architecture specific optimizations (on x64: '-march=haswell;-mavx2', on arm64: '-march=armv8.1-a')\"\n    },\n    \"nooptarch\": {\n      \"description\": \"Do _not_ use architecture specific optimizations (on x64: '-march=haswell;-mavx2', on arm64: '-march=armv8.1-a')\"\n    },\n    \"optsimd\": {\n      \"description\": \"Allow use of SIMD instructions (avx2 or neon) (requires 'optarch' to be enabled)\"\n    },\n    \"asm\": {\n      \"description\": \"Generate assembly files\"\n    }\n  }\n}"
  },
  {
    "path": "doc/doxyfile",
    "content": "# Doxyfile 1.11.0\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n#\n# Note:\n#\n# Use doxygen to compare the used configuration file with the template\n# configuration file:\n# doxygen -x [configFile]\n# Use doxygen to compare the used configuration file with the template\n# configuration file without replacing the environment variables or CMake type\n# replacement variables:\n# doxygen -x_noenv [configFile]\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = mi-malloc\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         = 1.8/2.1\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           = mimalloc-logo.svg\n\n# With the PROJECT_ICON tag one can specify an icon that is included in the tabs\n# when the HTML document is shown. Doxygen will copy the logo to the output\n# directory.\n\nPROJECT_ICON           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = ..\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096\n# sub-directories (in 2 levels) under the output directory of each output format\n# and will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to\n# control the number of sub-directories.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# Controls the number of sub-directories that will be created when\n# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every\n# level increment doubles the number of directories, resulting in 4096\n# directories at level 8 which is the default and also the maximum value. The\n# sub-directories are organized in 2 levels, the first level always has a fixed\n# number of 16 directories.\n# Minimum value: 0, maximum value: 8, default value: 8.\n# This tag requires that the tag CREATE_SUBDIRS is set to YES.\n\nCREATE_SUBDIRS_LEVEL   = 8\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,\n# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English\n# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,\n# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with\n# English messages), Korean, Korean-en (Korean with English messages), Latvian,\n# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,\n# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,\n# Swedish, Turkish, Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# By default Python docstrings are displayed as preformatted text and doxygen's\n# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the\n# doxygen's special commands can be used and the contents of the docstring\n# documentation blocks is shown as doxygen documentation.\n# The default value is: YES.\n\nPYTHON_DOCSTRING       = YES\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 2\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:^^\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". Note that you cannot put \\n's in the value part of an alias\n# to insert newlines (in the resulting output). You can put ^^ in the value part\n# of an alias to insert a newline as if a physical newline was in the original\n# file. When you need a literal { or } or , in the value part of an alias you\n# have to escape them by means of a backslash (\\), this can lead to conflicts\n# with the commands \\{ and \\} for these it is advised to use the version @{ and\n# @} or use a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,\n# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen. When specifying no_extension you should add\n# * to the FILE_PATTERNS.\n#\n# Note see also the list of default file extension mappings.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 6.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 0\n\n# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to\n# generate identifiers for the Markdown headings. Note: Every identifier is\n# unique.\n# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a\n# sequence number starting at 0 and GITHUB use the lower case version of title\n# with any whitespace replaced by '-' and punctuation characters removed.\n# The default value is: DOXYGEN.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nMARKDOWN_ID_STYLE      = DOXYGEN\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also makes the inheritance and\n# collaboration diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse\n# them like normal C++ but will assume all classes use public instead of private\n# inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = YES\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = YES\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use\n# during processing. When set to 0 doxygen will based this on the number of\n# cores available in the system. You can set it explicitly to a value larger\n# than 0 to get more control over the balance between CPU load and processing\n# speed. At this moment only the input processing can be done using multiple\n# threads. Since this is still an experimental feature the default is set to 1,\n# which effectively disables parallel processing. Please report any issues you\n# encounter. Generating dot graphs in parallel is controlled by the\n# DOT_NUM_THREADS setting.\n# Minimum value: 0, maximum value: 32, default value: 1.\n\nNUM_PROC_THREADS       = 1\n\n# If the TIMESTAMP tag is set different from NO then each generated page will\n# contain the date or date and time when the page was generated. Setting this to\n# NO can help when comparing the output of multiple runs.\n# Possible values are: YES, NO, DATETIME and DATE.\n# The default value is: NO.\n\nTIMESTAMP              = NO\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If this flag is set to YES, the name of an unnamed parameter in a declaration\n# will be determined by the corresponding definition. By default unnamed\n# parameters remain unnamed in the output.\n# The default value is: YES.\n\nRESOLVE_UNNAMED_PARAMS = YES\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# will also hide undocumented C++ concepts if enabled. This option has no effect\n# if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# With the correct setting of option CASE_SENSE_NAMES doxygen will better be\n# able to match the capabilities of the underlying filesystem. In case the\n# filesystem is case sensitive (i.e. it supports files in the same directory\n# whose names only differ in casing), the option must be set to YES to properly\n# deal with such files in case they appear in the input. For filesystems that\n# are not case sensitive the option should be set to NO to properly deal with\n# output files written for symbols that only differ in casing, such as for two\n# classes, one named CLASS and the other named Class, and to also support\n# references to files without having to specify the exact matching casing. On\n# Windows (including Cygwin) and MacOS, users should typically set this option\n# to NO, whereas on Linux or other Unix flavors it should typically be set to\n# YES.\n# Possible values are: SYSTEM, NO and YES.\n# The default value is: SYSTEM.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class\n# will show which file needs to be included to use the class.\n# The default value is: YES.\n\nSHOW_HEADERFILE        = YES\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 0\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = NO\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = NO\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file. See also section \"Changing the\n# layout of pages\" for information.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as documenting some parameters in\n# a documented function twice, or documenting parameters that don't exist or\n# using markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete\n# function parameter documentation. If set to NO, doxygen will accept that some\n# parameters have no documentation without warning.\n# The default value is: YES.\n\nWARN_IF_INCOMPLETE_DOC = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong parameter\n# documentation, but not about the absence of documentation. If EXTRACT_ALL is\n# set to YES then this flag will automatically be disabled. See also\n# WARN_IF_INCOMPLETE_DOC\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about\n# undocumented enumeration values. If set to NO, doxygen will accept\n# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: NO.\n\nWARN_IF_UNDOC_ENUM_VAL = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS\n# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but\n# at the end of the doxygen process doxygen will return with a non-zero status.\n# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves\n# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not\n# write the warning messages in between other messages but write them at the end\n# of a run, in case a WARN_LOGFILE is defined the warning messages will be\n# besides being in the defined file also be shown at the end of a run, unless\n# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case\n# the behavior will remain as with the setting FAIL_ON_WARNINGS.\n# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# See also: WARN_LINE_FORMAT\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# In the $text part of the WARN_FORMAT command it is possible that a reference\n# to a more specific place is given. To make it easier to jump to this place\n# (outside of doxygen) the user can define a custom \"cut\" / \"paste\" string.\n# Example:\n# WARN_LINE_FORMAT = \"'vi $file +$line'\"\n# See also: WARN_FORMAT\n# The default value is: at line $line of file $file.\n\nWARN_LINE_FORMAT       = \"at line $line of file $file\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr). In case the file specified cannot be opened for writing the\n# warning and error messages are written to standard error. When as file - is\n# specified the warning and error messages are written to standard output\n# (stdout).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = mimalloc-doc.h\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see:\n# https://www.gnu.org/software/libiconv/) for the list of possible encodings.\n# See also: INPUT_FILE_ENCODING\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify\n# character encoding on a per file pattern basis. Doxygen will compare the file\n# name with each pattern and apply the encoding instead of the default\n# INPUT_ENCODING) if there is a match. The character encodings are a list of the\n# form: pattern=encoding (like *.php=ISO-8859-1).\n# See also: INPUT_ENCODING for further information on supported encodings.\n\nINPUT_FILE_ENCODING    =\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# Note the list of default checked file patterns might differ from the list of\n# default file extension mappings.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,\n# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl,\n# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d,\n# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to\n# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.c \\\n                         *.cc \\\n                         *.cxx \\\n                         *.cpp \\\n                         *.c++ \\\n                         *.java \\\n                         *.ii \\\n                         *.ixx \\\n                         *.ipp \\\n                         *.i++ \\\n                         *.inl \\\n                         *.idl \\\n                         *.ddl \\\n                         *.odl \\\n                         *.h \\\n                         *.hh \\\n                         *.hxx \\\n                         *.hpp \\\n                         *.h++ \\\n                         *.cs \\\n                         *.d \\\n                         *.php \\\n                         *.php4 \\\n                         *.php5 \\\n                         *.phtml \\\n                         *.inc \\\n                         *.m \\\n                         *.markdown \\\n                         *.md \\\n                         *.mm \\\n                         *.dox \\\n                         *.py \\\n                         *.pyw \\\n                         *.f90 \\\n                         *.f95 \\\n                         *.f03 \\\n                         *.f08 \\\n                         *.f \\\n                         *.for \\\n                         *.tcl \\\n                         *.vhd \\\n                         *.vhdl \\\n                         *.ucf \\\n                         *.qsf\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = NO\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# ANamespace::AClass, ANamespace::*Test\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that doxygen will use the data processed and written to standard output\n# for further processing, therefore nothing else, like debug statements or used\n# commands (so in case of a Windows batch file always use @echo OFF), should be\n# written to standard output.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n# The Fortran standard specifies that for fixed formatted Fortran code all\n# characters from position 72 are to be considered as comment. A common\n# extension is to allow longer lines before the automatic comment starts. The\n# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can\n# be processed before the automatic comment starts.\n# Minimum value: 7, maximum value: 10000, default value: 72.\n\nFORTRAN_COMMENT_AFTER  = 72\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# multi-line macros, enums or list initialized variables directly into the\n# documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see:\n# http://clang.llvm.org/) for more accurate parsing at the cost of reduced\n# performance. This can be particularly helpful with template rich C++ code for\n# which doxygen's built-in parser lacks the necessary type information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = NO\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS\n# tag is set to YES then doxygen will add the directory of each input to the\n# include path.\n# The default value is: YES.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_ADD_INC_PATHS    = YES\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          =\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the directory containing a file called compile_commands.json. This\n# file is the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the\n# options used when the source files were built. This is equivalent to\n# specifying the -p option to a clang tool, such as clang-check. These options\n# will then be passed to the parser. Any options specified with CLANG_OPTIONS\n# will be added as well.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)\n# that should be ignored while generating the index headers. The IGNORE_PREFIX\n# tag works for classes, function and member names. The entity will be placed in\n# the alphabetical list under the first letter of the entity name that remains\n# after removing the prefix.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = docs\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# Note: Since the styling of scrollbars can currently not be overruled in\n# Webkit/Chromium, the styling will be left out of the default doxygen.css if\n# one or more extra stylesheets have been specified. So if scrollbar\n# customization is desired it has to be added explicitly. For an example see the\n# documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  = mimalloc-doxygen.css\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output\n# should be rendered with a dark or light theme.\n# Possible values are: LIGHT always generates light mode output, DARK always\n# generates dark mode output, AUTO_LIGHT automatically sets the mode according\n# to the user preference, uses light mode if no preference is set (the default),\n# AUTO_DARK automatically sets the mode according to the user preference, uses\n# dark mode if no preference is set and TOGGLE allows a user to switch between\n# light and dark mode via a button.\n# The default value is: AUTO_LIGHT.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE        = LIGHT\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a color-wheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 189\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use gray-scales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 12\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 240\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = NO\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be\n# dynamically folded and expanded in the generated HTML source code.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_CODE_FOLDING      = YES\n\n# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in\n# the top right corner of code and text fragments that allows the user to copy\n# its content to the clipboard. Note this only works if supported by the browser\n# and the web page is served via a secure context (see:\n# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file:\n# protocol.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COPY_CLIPBOARD    = YES\n\n# Doxygen stores a couple of settings persistently in the browser (via e.g.\n# cookies). By default these settings apply to all HTML pages generated by\n# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store\n# the settings under a project specific key, such that the user preferences will\n# be stored separately.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_PROJECT_COOKIE    =\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see:\n# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To\n# create a documentation set, doxygen will generate a Makefile in the HTML\n# output directory. Running make will produce the docset in that directory and\n# running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag determines the URL of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDURL         =\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# on Windows. In the beginning of 2021 Microsoft took the original page, with\n# a.o. the download links, offline the HTML help workshop was already many years\n# in maintenance mode). You can download the HTML help workshop from the web\n# archives at Installation executable (see:\n# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo\n# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the main .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# The SITEMAP_URL tag is used to specify the full URL of the place where the\n# generated documentation will be placed on the server by the user during the\n# deployment of the documentation. The generated sitemap is called sitemap.xml\n# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL\n# is specified no sitemap is generated. For information about the sitemap\n# protocol see https://www.sitemaps.org\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSITEMAP_URL            =\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location (absolute path\n# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to\n# run qhelpgenerator on the generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = YES\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine tune the look of the index (see \"Fine-tuning the output\"). As an\n# example, the default style sheet generated by doxygen has an example that\n# shows how to put an image at the root of the tree instead of the PROJECT_NAME.\n# Since the tree basically has the same information as the tab index, you could\n# consider setting DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the\n# FULL_SIDEBAR option determines if the side bar is limited to only the treeview\n# area (value NO) or if it should extend to the full height of the window (value\n# YES). Setting this to YES gives a layout similar to\n# https://docs.readthedocs.io with more room for contents, but less room for the\n# project logo, title, and description. If either GENERATE_TREEVIEW or\n# DISABLE_INDEX is set to NO, this option has no effect.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFULL_SIDEBAR           = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 180\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email\n# addresses.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nOBFUSCATE_EMAILS       = YES\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png (the default) and svg (looks nicer but requires the\n# pdf2svg or inkscape tool).\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.\n# Note that the different versions of MathJax have different requirements with\n# regards to the different settings, so it is possible that also other MathJax\n# settings have to be changed when switching between the different MathJax\n# versions.\n# Possible values are: MathJax_2 and MathJax_3.\n# The default value is: MathJax_2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_VERSION        = MathJax_2\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. For more details about the output format see MathJax\n# version 2 (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3\n# (see:\n# http://docs.mathjax.org/en/latest/web/components/output.html).\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility. This is the name for Mathjax version 2, for MathJax version 3\n# this will be translated into chtml), NativeMML (i.e. MathML. Only supported\n# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This\n# is the name for Mathjax version 3, for MathJax version 2 this will be\n# translated into HTML-CSS) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment. The default value is:\n# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2\n# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# for MathJax version 2 (see\n# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# For example for MathJax version 3 (see\n# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):\n# MATHJAX_EXTENSIONS = ams\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/). See the section \"External Indexing and Searching\" for\n# details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = \\makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for\n# the generated LaTeX document. The header should contain everything until the\n# first chapter. If it is left blank doxygen will generate a standard header. It\n# is highly recommended to start with a default header using\n# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty\n# and then modify the file new_header.tex. See also section \"Doxygen usage\" for\n# information on how to generate the default header that doxygen normally uses.\n#\n# Note: Only use a user-defined header if you know what you are doing!\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. The following\n# commands have a special meaning inside the header (and footer): For a\n# description of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for\n# the generated LaTeX document. The footer should contain everything after the\n# last chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer. See also section \"Doxygen\n# usage\" for information on how to generate the default footer that doxygen\n# normally uses. Note: Only use a user-defined footer if you know what you are\n# doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as\n# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX\n# files. Set this option to YES, to get a higher quality PDF documentation.\n#\n# See also section LATEX_CMD_NAME for selecting the engine.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.\n# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch\n# mode nothing is printed on the terminal, errors are scrolled as if <return> is\n# hit at every error; missing files that TeX tries to input or request from\n# keyboard input (\\read on a not open input stream) cause the job to abort,\n# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,\n# but there is no possibility of user interaction just like in batch mode,\n# SCROLL In scroll mode, TeX will stop only for missing files to input or if\n# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at\n# each error, asking for user intervention.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the RTF_OUTPUT output directory.\n# Note that the files will be copied as-is; there are no commands or markers\n# available.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTRA_FILES        =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to Sqlite3 output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3\n# database with symbols found by doxygen stored in tables.\n# The default value is: NO.\n\nGENERATE_SQLITE3       = NO\n\n# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be\n# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put\n# in front of it.\n# The default directory is: sqlite3.\n# This tag requires that the tag GENERATE_SQLITE3 is set to YES.\n\nSQLITE3_OUTPUT         = sqlite3\n\n# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db\n# database file will be recreated with each doxygen run. If set to NO, doxygen\n# will warn if a database file is already found and not modify it.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_SQLITE3 is set to YES.\n\nSQLITE3_RECREATE_DB    = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of\n# RECURSIVE has no effect here.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces\n# will be listed in the class and namespace index. If set to NO, only the\n# inherited external classes will be listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the topic index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to diagram generator tools\n#---------------------------------------------------------------------------\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of\n# subgraphs. When you want a differently looking font in the dot files that\n# doxygen generates you can specify fontname, fontcolor and fontsize attributes.\n# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,\n# Edge and Graph Attributes specification</a> You need to make sure dot is able\n# to find the font, which can be done by putting it in a standard location or by\n# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the\n# directory containing the font. Default graphviz fontsize is 14.\n# The default value is: fontname=Helvetica,fontsize=10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_COMMON_ATTR        = \"fontname=Helvetica,fontsize=10\"\n\n# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can\n# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a\n# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about\n# arrows shapes.</a>\n# The default value is: labelfontname=Helvetica,labelfontsize=10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_EDGE_ATTR          = \"labelfontname=Helvetica,labelfontsize=10\"\n\n# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes\n# around nodes set 'shape=plain' or 'shape=plaintext' <a\n# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>\n# The default value is: shape=box,height=0.2,width=0.4.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NODE_ATTR          = \"shape=box,height=0.2,width=0.4\"\n\n# You can set the path where dot can find font specified with fontname in\n# DOT_COMMON_ATTR and others dot attributes.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will\n# generate a graph for each documented class showing the direct and indirect\n# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and\n# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case\n# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the\n# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.\n# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance\n# relations will be shown as texts / links. Explicit enabling an inheritance\n# graph or choosing a different representation for an inheritance graph of a\n# specific class, can be accomplished by means of the command \\inheritancegraph.\n# Disabling an inheritance graph can be accomplished by means of the command\n# \\hideinheritancegraph.\n# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.\n# The default value is: YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes. Explicit enabling a collaboration graph,\n# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the\n# command \\collaborationgraph. Disabling a collaboration graph can be\n# accomplished by means of the command \\hidecollaborationgraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies. Explicit enabling a group\n# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means\n# of the command \\groupgraph. Disabling a directory graph can be accomplished by\n# means of the command \\hidegroupgraph. See also the chapter Grouping in the\n# manual.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and\n# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS\n# tag is set to YES, doxygen will add type and arguments for attributes and\n# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen\n# will not generate fields with class member information in the UML graphs. The\n# class diagrams will look similar to the default class diagrams but using UML\n# notation for the relationships.\n# Possible values are: NO, YES and NONE.\n# The default value is: NO.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nDOT_UML_DETAILS        = NO\n\n# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters\n# to display on a single line. If the actual line length exceeds this threshold\n# significantly it will be wrapped across multiple lines. Some heuristics are\n# applied to avoid ugly line breaks.\n# Minimum value: 0, maximum value: 1000, default value: 17.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_WRAP_THRESHOLD     = 17\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,\n# can be accomplished by means of the command \\includegraph. Disabling an\n# include graph can be accomplished by means of the command \\hideincludegraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set\n# to NO, can be accomplished by means of the command \\includedbygraph. Disabling\n# an included by graph can be accomplished by means of the command\n# \\hideincludedbygraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories. Explicit enabling a directory graph, when\n# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command\n# \\directorygraph. Disabling a directory graph can be accomplished by means of\n# the command \\hidedirectorygraph.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels\n# of child directories generated in directory dependency graphs by dot.\n# Minimum value: 1, maximum value: 25, default value: 1.\n# This tag requires that the tag DIRECTORY_GRAPH is set to YES.\n\nDIR_GRAPH_MAX_DEPTH    = 1\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# https://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file or to the filename of jar file\n# to be used. If left blank, it is assumed PlantUML is not used or called during\n# a preprocessing step. Doxygen will generate a warning when it encounters a\n# \\startuml command in this case and will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal\n# graphical representation for inheritance and collaboration diagrams is used.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate\n# files that are used to generate the various graphs.\n#\n# Note: This setting is not only used for dot files but also for msc temporary\n# files.\n# The default value is: YES.\n\nDOT_CLEANUP            = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will\n# use a built-in version of mscgen tool to produce the charts. Alternatively,\n# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,\n# specifying prog as the value, doxygen will call the tool as prog -T\n# <outfile_format> -o <outputfile> <inputfile>. The external tool should support\n# output file formats \"png\", \"eps\", \"svg\", and \"ismap\".\n\nMSCGEN_TOOL            =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n"
  },
  {
    "path": "doc/mimalloc-doc.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2025, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#error \"documentation file only!\"\n\n\n/*! \\mainpage\n\nThis is the API documentation of the\n[mimalloc](https://github.com/microsoft/mimalloc) allocator\n(pronounced \"me-malloc\") -- a\ngeneral purpose allocator with excellent [performance](bench.html)\ncharacteristics. Initially\ndeveloped by Daan Leijen for the run-time systems of the\n[Koka](https://github.com/koka-lang/koka) and [Lean](https://github.com/leanprover/lean) languages.\n\nIt is a drop-in replacement for `malloc` and can be used in other programs\nwithout code changes, for example, on Unix you can use it as:\n```\n> LD_PRELOAD=/usr/bin/libmimalloc.so  myprogram\n```\n\nNotable aspects of the design include:\n- __small and consistent__: the core library is about 10k LOC using simple and\n  consistent data structures. This makes it very suitable\n  to integrate and adapt in other projects. For runtime systems it\n  provides hooks for a monotonic _heartbeat_ and deferred freeing (for\n  bounded worst-case times with reference counting).\n  Partly due to its simplicity, mimalloc has been ported to many systems (Windows, macOS,\n  Linux, WASM, various BSD's, Haiku, MUSL, etc) and has excellent support for dynamic overriding.\n  At the same time, it is an industrial strength allocator that runs (very) large scale\n  distributed services on thousands of machines with excellent worst case latencies.\n- __free list sharding__: instead of one big free list (per size class) we have\n  many smaller lists per \"mimalloc page\" which reduces fragmentation and\n  increases locality --\n  things that are allocated close in time get allocated close in memory.\n  (A mimalloc page contains blocks of one size class and is usually 64KiB on a 64-bit system).\n- __free list multi-sharding__: the big idea! Not only do we shard the free list\n  per mimalloc page, but for each page we have multiple free lists. In particular, there\n  is one list for thread-local `free` operations, and another one for concurrent `free`\n  operations. Free-ing from another thread can now be a single CAS without needing\n  sophisticated coordination between threads. Since there will be\n  thousands of separate free lists, contention is naturally distributed over the heap,\n  and the chance of contending on a single location will be low -- this is quite\n  similar to randomized algorithms like skip lists where adding\n  a random oracle removes the need for a more complex algorithm.\n- __eager page purging__: when a \"page\" becomes empty (with increased chance\n  due to free list sharding) the memory is marked to the OS as unused (reset or decommitted)\n  reducing (real) memory pressure and fragmentation, especially in long running\n  programs.\n- __secure__: _mimalloc_ can be built in secure mode, adding guard pages,\n  randomized allocation, encrypted free lists, etc. to protect against various\n  heap vulnerabilities. The performance penalty is usually around 10% on average\n  over our benchmarks.\n- __first-class heaps__: efficiently create and use multiple heaps to allocate across different regions.\n  A heap can be destroyed at once instead of deallocating each object separately.\n  v3.2+ has true first-class heaps where one can allocate in a heap from any thread.   \n- __bounded__: it does not suffer from _blowup_ \\[1\\], has bounded worst-case allocation\n  times (_wcat_) (upto OS primitives), bounded space overhead (~0.2% meta-data, with low\n  internal fragmentation), and has no internal points of contention using only atomic operations.\n- __fast__: In our benchmarks (see [below](#bench)),\n  _mimalloc_ outperforms other leading allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc),\n  and often uses less memory. A nice property is that it does consistently well over a wide range\n  of benchmarks. There is also good huge OS page support for larger server programs.\n\nThere are three maintained versions of mimalloc. All versions are mostly equal except for \nhow the OS memory is handled. New development is mostly on v3, while v1 and v2 are maintained \nwith security and bug fixes. \n\n- __v1__: initial design of mimalloc (release tags: `v1.9.x`, development branch `dev`). Send PR's against this version if possible.\n- __v2__: main mimalloc version. Uses thread-local segments to reduce fragmentation. (release tags: `v2.2.x`, development branch `dev2`)\n- __v3__: simplifies the lock-free design of previous versions, and improves sharing of \n        memory between threads. On certain large workloads this version may use \n        (much) less memory. Also supports true first-class heaps (that can allocate from any thread) \n        and has more efficient heap-walking (for the CPython GC for example).\n        (release tags: `v3.2.x`, development branch `dev3`).\n\nYou can read more on the design of mimalloc in the\n[technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action)\nwhich also has detailed benchmark results.\nTo learn more about the internal implementation of mimalloc, see [include/mimalloc/types.h](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc/types.h).\n\nFurther information:\n\n- \\ref build\n- \\ref using\n- \\ref environment\n- \\ref overrides\n- \\ref modes\n- \\ref tools\n- \\ref bench\n\n&nbsp;\n\n- \\ref malloc\n- \\ref aligned\n- \\ref typed\n- \\ref zeroinit\n\n- \\ref heap\n- \\ref analysis\n- \\ref arenas\n- \\ref subproc\n\n- \\ref extended\n- \\ref options\n- \\ref theap\n- \\ref posix\n- \\ref cpp\n\n*/\n\n\n/// \\defgroup malloc Basic Allocation\n/// The basic allocation interface.\n/// \\{\n\n\n/// Free previously allocated memory.\n/// The pointer `p` must have been allocated before (or be \\a NULL).\n/// @param p  pointer to free, or \\a NULL.\nvoid  mi_free(void* p);\n\n/// Allocate \\a size bytes.\n/// @param size  number of bytes to allocate.\n/// @returns pointer to the allocated memory or \\a NULL if out of memory.\n/// Returns a unique pointer if called with \\a size 0.\nvoid* mi_malloc(size_t size);\n\n/// Allocate zero-initialized `size` bytes.\n/// @param size The size in bytes.\n/// @returns Pointer to newly allocated zero initialized memory,\n/// or \\a NULL if out of memory.\nvoid* mi_zalloc(size_t size);\n\n/// Allocate zero-initialized \\a count elements of \\a size bytes.\n/// @param count number of elements.\n/// @param size  size of each element.\n/// @returns pointer to the allocated memory\n/// of \\a size*\\a count bytes, or \\a NULL if either out of memory\n/// or when `count*size` overflows.\n///\n/// Returns a unique pointer if called with either \\a size or \\a count of 0.\n/// @see mi_zalloc()\nvoid* mi_calloc(size_t count, size_t size);\n\n/// Re-allocate memory to \\a newsize bytes.\n/// @param p  pointer to previously allocated memory (or \\a NULL).\n/// @param newsize  the new required size in bytes.\n/// @returns pointer to the re-allocated memory\n/// of \\a newsize bytes, or \\a NULL if out of memory.\n/// If \\a NULL is returned, the pointer \\a p is not freed.\n/// Otherwise the original pointer is either freed or returned\n/// as the reallocated result (in case it fits in-place with the\n/// new size). If the pointer \\a p is \\a NULL, it behaves as\n/// \\a mi_malloc(\\a newsize). If \\a newsize is larger than the\n/// original \\a size allocated for \\a p, the bytes after \\a size\n/// are uninitialized.\n/// @see mi_reallocf()\nvoid* mi_realloc(void* p, size_t newsize);\n\n/// Try to re-allocate memory to \\a newsize bytes _in place_.\n/// @param p  pointer to previously allocated memory (or \\a NULL).\n/// @param newsize  the new required size in bytes.\n/// @returns pointer to the re-allocated memory\n/// of \\a newsize bytes (always equal to \\a p),\n/// or \\a NULL if either out of memory or if\n/// the memory could not be expanded in place.\n/// If \\a NULL is returned, the pointer \\a p is not freed.\n/// Otherwise the original pointer is returned\n/// as the reallocated result since it fits in-place with the\n/// new size. If \\a newsize is larger than the\n/// original \\a size allocated for \\a p, the bytes after \\a size\n/// are uninitialized.\nvoid* mi_expand(void* p, size_t newsize);\n\n/// Allocate \\a count elements of \\a size bytes.\n/// @param count The number of elements.\n/// @param size The size of each element.\n/// @returns A pointer to a block of \\a count * \\a size bytes, or \\a NULL\n/// if out of memory or if \\a count * \\a size overflows.\n///\n/// If there is no overflow, it behaves exactly like `mi_malloc(count*size)`.\n/// @see mi_calloc()\n/// @see mi_zallocn()\nvoid* mi_mallocn(size_t count, size_t size);\n\n/// Re-allocate memory to \\a count elements of \\a size bytes.\n/// @param p Pointer to a previously allocated block (or \\a NULL).\n/// @param count The number of elements.\n/// @param size The size of each element.\n/// @returns A pointer to a re-allocated block of \\a count * \\a size bytes, or \\a NULL\n/// if out of memory or if \\a count * \\a size overflows.\n///\n/// If there is no overflow, it behaves exactly like `mi_realloc(p,count*size)`.\n/// @see [reallocarray()](<http://man.openbsd.org/reallocarray>) (on BSD)\nvoid* mi_reallocn(void* p, size_t count, size_t size);\n\n/// Re-allocate memory to \\a newsize bytes,\n/// @param p  pointer to previously allocated memory (or \\a NULL).\n/// @param newsize  the new required size in bytes.\n/// @returns pointer to the re-allocated memory\n/// of \\a newsize bytes, or \\a NULL if out of memory.\n///\n/// In contrast to mi_realloc(), if \\a NULL is returned, the original pointer\n/// \\a p is freed (if it was not \\a NULL itself).\n/// Otherwise the original pointer is either freed or returned\n/// as the reallocated result (in case it fits in-place with the\n/// new size). If the pointer \\a p is \\a NULL, it behaves as\n/// \\a mi_malloc(\\a newsize). If \\a newsize is larger than the\n/// original \\a size allocated for \\a p, the bytes after \\a size\n/// are uninitialized.\n///\n/// @see [reallocf](https://www.freebsd.org/cgi/man.cgi?query=reallocf) (on BSD)\nvoid* mi_reallocf(void* p, size_t newsize);\n\n\n/// Allocate and duplicate a string.\n/// @param s string to duplicate (or \\a NULL).\n/// @returns a pointer to newly allocated memory initialized\n/// to string \\a s, or \\a NULL if either out of memory or if\n/// \\a s is \\a NULL.\n///\n/// Replacement for the standard [strdup()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/strdup.html)\n/// such that mi_free() can be used on the returned result.\nchar* mi_strdup(const char* s);\n\n/// Allocate and duplicate a string up to \\a n bytes.\n/// @param s string to duplicate (or \\a NULL).\n/// @param n maximum number of bytes to copy (excluding the terminating zero).\n/// @returns a pointer to newly allocated memory initialized\n/// to string \\a s up to the first \\a n bytes (and always zero terminated),\n/// or \\a NULL if either out of memory or if \\a s is \\a NULL.\n///\n/// Replacement for the standard [strndup()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/strndup.html)\n/// such that mi_free() can be used on the returned result.\nchar* mi_strndup(const char* s, size_t n);\n\n/// Resolve a file path name.\n/// @param fname File name.\n/// @param resolved_name Should be \\a NULL (but can also point to a buffer\n///                      of at least \\a PATH_MAX bytes).\n/// @returns If successful a pointer to the resolved absolute file name, or\n/// \\a NULL on failure (with \\a errno set to the error code).\n///\n/// If \\a resolved_name was \\a NULL, the returned result should be freed with\n/// mi_free().\n///\n/// Replacement for the standard [realpath()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html)\n/// such that mi_free() can be used on the returned result (if \\a resolved_name was \\a NULL).\nchar* mi_realpath(const char* fname, char* resolved_name);\n\n/// Return the available bytes in a memory block.\n/// @param p Pointer to previously allocated memory (or \\a NULL)\n/// @returns Returns the available bytes in the memory block, or\n/// 0 if \\a p was \\a NULL.\n///\n/// The returned size can be\n/// used to call \\a mi_expand successfully.\n/// The returned size is always at least equal to the\n/// allocated size of \\a p.\n///\n/// @see [_msize](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/msize?view=vs-2017) (Windows)\n/// @see [malloc_usable_size](http://man7.org/linux/man-pages/man3/malloc_usable_size.3.html) (Linux)\n/// @see mi_good_size()\nsize_t mi_usable_size(void* p);\n\n/// Return the probable allocation block size for a given required size.\n/// @param size The minimal required size in bytes.\n/// @returns the size `n` that will be allocated, where `n >= size`.\n///\n/// Generally, `mi_usable_size(mi_malloc(size)) == mi_good_size(size)`.\n/// This function can be used to reduce internal wasted space when\n/// allocating buffers for example.\n///\n/// @see mi_usable_size()\nsize_t mi_good_size(size_t size);\n\n/// \\}\n\n\n// ------------------------------------------------------\n// Aligned allocation\n// ------------------------------------------------------\n\n/// \\defgroup aligned Aligned Allocation\n/// Allocating aligned memory blocks.\n///\n/// Note that `alignment` always follows `size` for consistency with the unaligned\n/// allocation API, but unfortunately this differs from `posix_memalign` and `aligned_alloc` in the C library.\n///\n/// \\{\n\n/// Allocate \\a size bytes aligned by \\a alignment.\n/// @param size  number of bytes to allocate.\n/// @param alignment  the minimal alignment of the allocated memory.\n/// @returns pointer to the allocated memory or \\a NULL if out of memory,\n/// or if the alignment is not a power of 2 (including 0). The \\a size is unrestricted\n/// (and does not have to be an integral multiple of the \\a alignment).\n/// The returned pointer is aligned by \\a alignment, i.e. `(uintptr_t)p % alignment == 0`.\n/// Returns a unique pointer if called with \\a size 0.\n///\n/// Note that `alignment` always follows `size` for consistency with the unaligned\n/// allocation API, but unfortunately this differs from `posix_memalign` and `aligned_alloc` in the C library.\n///\n/// @see [aligned_alloc](https://en.cppreference.com/w/c/memory/aligned_alloc) (in the standard C11 library, with switched arguments!)\n/// @see [_aligned_malloc](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc?view=vs-2017) (on Windows)\n/// @see [aligned_alloc](http://man.openbsd.org/reallocarray) (on BSD, with switched arguments!)\n/// @see [posix_memalign](https://linux.die.net/man/3/posix_memalign) (on Posix, with switched arguments!)\n/// @see [memalign](https://linux.die.net/man/3/posix_memalign) (on Linux, with switched arguments!)\nvoid* mi_malloc_aligned(size_t size, size_t alignment);\nvoid* mi_zalloc_aligned(size_t size, size_t alignment);\nvoid* mi_calloc_aligned(size_t count, size_t size, size_t alignment);\nvoid* mi_realloc_aligned(void* p, size_t newsize, size_t alignment);\n\n/// Allocate \\a size bytes aligned by \\a alignment at a specified \\a offset.\n/// @param size  number of bytes to allocate.\n/// @param alignment  the minimal alignment of the allocated memory at \\a offset.\n/// @param offset     the offset that should be aligned.\n/// @returns pointer to the allocated memory or \\a NULL if out of memory,\n/// or if the alignment is not a power of 2 (including 0). The \\a size is unrestricted\n/// (and does not have to be an integral multiple of the \\a alignment).\n/// The returned pointer is aligned by \\a alignment, i.e. `(uintptr_t)p % alignment == 0`.\n/// Returns a unique pointer if called with \\a size 0.\n///\n/// @see [_aligned_offset_malloc](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-offset-malloc?view=vs-2017) (on Windows)\nvoid* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset);\nvoid* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset);\nvoid* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset);\nvoid* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset);\n\n/// \\}\n\n\n/// \\defgroup typed Typed Macros\n/// Typed allocation macros. \n/// For example:\n/// ```\n/// int* p = mi_malloc_tp(int)\n/// ```\n///\n/// \\{\n\n/// Allocate a block of type \\a tp.\n/// @param tp The type of the block to allocate.\n/// @returns A pointer to an object of type \\a tp, or\n/// \\a NULL if out of memory.\n///\n/// **Example:**\n/// ```\n/// int* p = mi_malloc_tp(int)\n/// ```\n///\n/// @see mi_malloc()\n#define mi_malloc_tp(tp)        ((tp*)mi_malloc(sizeof(tp)))\n\n/// Allocate a zero-initialized block of type \\a tp.\n#define mi_zalloc_tp(tp)        ((tp*)mi_zalloc(sizeof(tp)))\n\n/// Allocate \\a count zero-initialized blocks of type \\a tp.\n#define mi_calloc_tp(tp,count)      ((tp*)mi_calloc(count,sizeof(tp)))\n\n/// Allocate \\a count blocks of type \\a tp.\n#define mi_mallocn_tp(tp,count)     ((tp*)mi_mallocn(count,sizeof(tp)))\n\n/// Re-allocate to \\a count blocks of type \\a tp.\n#define mi_reallocn_tp(p,tp,count)  ((tp*)mi_reallocn(p,count,sizeof(tp)))\n\n/// Allocate a block of type \\a tp in a heap \\a hp.\n#define mi_heap_malloc_tp(tp,hp)        ((tp*)mi_heap_malloc(hp,sizeof(tp)))\n\n/// Allocate a zero-initialized block of type \\a tp in a heap \\a hp.\n#define mi_heap_zalloc_tp(tp,hp)        ((tp*)mi_heap_zalloc(hp,sizeof(tp)))\n\n/// Allocate \\a count zero-initialized blocks of type \\a tp in a heap \\a hp.\n#define mi_heap_calloc_tp(tp,hp,count)      ((tp*)mi_heap_calloc(hp,count,sizeof(tp)))\n\n/// Allocate \\a count blocks of type \\a tp in a heap \\a hp.\n#define mi_heap_mallocn_tp(tp,hp,count)     ((tp*)mi_heap_mallocn(hp,count,sizeof(tp)))\n\n/// Re-allocate to \\a count blocks of type \\a tp in a heap \\a hp.\n#define mi_heap_reallocn_tp(tp,hp,p,count)  ((tp*)mi_heap_reallocn(p,count,sizeof(tp)))\n\n/// Re-allocate to \\a count zero initialized blocks of type \\a tp in a heap \\a hp.\n#define mi_heap_recalloc_tp(tp,hp,p,count)  ((tp*)mi_heap_recalloc(p,count,sizeof(tp)))\n\n/// \\}\n\n/// \\defgroup zeroinit Zero initialized re-allocation\n/// Re-allocation of zero initialized blocks.\n///\n/// __The zero-initialized re-allocations are only valid on memory that was\n/// originally allocated with zero initialization too,__\n/// e.g. `mi_calloc`, `mi_zalloc`, `mi_zalloc_aligned` etc.\n/// see also <https://github.com/microsoft/mimalloc/issues/63#issuecomment-508272992>\n///\n/// \\{\n\n/// Re-allocate memory to \\a count elements of \\a size bytes, with extra memory initialized to zero.\n/// @param p Pointer to a previously allocated block (or \\a NULL).\n/// @param count The number of elements.\n/// @param size The size of each element.\n/// @returns A pointer to a re-allocated block of \\a count * \\a size bytes, or \\a NULL\n/// if out of memory or if \\a count * \\a size overflows.\n///\n/// If there is no overflow, it behaves exactly like `mi_rezalloc(p,count*size)`.\n/// @see mi_reallocn()\n/// @see [recallocarray()](http://man.openbsd.org/reallocarray) (on BSD).\nvoid* mi_recalloc(void* p, size_t count, size_t size);\n\nvoid* mi_rezalloc(void* p, size_t newsize);\nvoid* mi_recalloc(void* p, size_t newcount, size_t size) ;\n\nvoid* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment);\nvoid* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset);\nvoid* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment);\nvoid* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset);\n\nvoid* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize);\nvoid* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size);\n\nvoid* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment);\nvoid* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset);\nvoid* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment);\nvoid* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset);\n\n/// \\}\n\n\n\n/// \\defgroup heap Heaps\n/// First-class heaps. \n/// Heaps allow allocations to be grouped, and for example be \n/// destroyed in one go. Heaps can also be associated with \n/// a specific allocation arena (`mi_heap_new_in_arena()`).\n///\n/// __v1__,__v2__: heaps are only semi-first-class and \n///    __one can only use heap allocation functions from the thread that created the heap__.\n///    (of course, mi_free() can always be used from any thread to free objects from any heap).\n///\n/// __v3__: heaps are fully first-class and can be used to allocate efficiently from\n///    from any thread. \n///    In v3, the old v1/v2 heaps still exist but are now called _theaps_ (mi_theap_t()) for \n///    thread-local heaps. A v3 heap creates internally such theaps on demand\n///    to efficiently allocate without needing taking locks for example.\n///\n/// \\{\n\n/// Type of first-class heaps.\n///\n/// __v1__,__v2__: in mimalloc v1 and v2, \n/// a heap can only be used for allocation in\n/// the thread that created this heap!\nstruct mi_heap_s;\n\n/// Type of first-class heaps.\n/// \n/// __v1__,__v2__: in mimalloc v1 and v2, \n/// a heap can only be used for allocation in\n/// the thread that created this heap!\ntypedef struct mi_heap_s mi_heap_t;\n\n/// Create a new heap that can be used for allocation.\nmi_heap_t* mi_heap_new();\n\n/// Delete a previously allocated heap.\n/// This will release internal resources but not free \n/// still allocated blocks in this heap (and should be \n/// `mi_free`'d later on as usual).\n///\n/// Note: trying to delete the main heap of a subprocess is ignored.\nvoid mi_heap_delete(mi_heap_t* heap);\n\n/// Destroy a heap, freeing all its still allocated blocks.\n/// Use with care as this will free all blocks still\n/// allocated in the heap. However, this can be an\n/// efficient way to free all heap memory in one go.\n///\n/// Note: trying to destroy the main heap of a subprocess is ignored.\nvoid mi_heap_destroy(mi_heap_t* heap);\n\n/// __v1__,__v2__: Set the default heap to use in the current thread for mi_malloc() et al.\n/// @param heap  The new default heap.\n/// @returns The previous default heap.\n/// @see mi_theap_set_default() for __v3__\nmi_heap_t* mi_heap_set_default(mi_heap_t* heap);\n\n/// __v1__,__v2__: Get the default heap that is used for mi_malloc() et al. (for the current thread).\n/// @returns The current default heap.\n/// @see mi_theap_get_default() for __v3__\nmi_heap_t* mi_heap_get_default();\n\n/// __v1__,__v2__: Get the backing heap.\n/// The _backing_ heap is the initial default heap for\n/// a thread and always available for allocations.\n/// It cannot be destroyed or deleted\n/// except by exiting the thread.\n/// @see mi_heap_main() for __v3__\nmi_heap_t* mi_heap_get_backing();\n\n/// @brief __v3__: set NUMA affinity for a heap.\n/// @param heap the heap which should be associated with a specific numa node.\n/// @param numa_node the numa node to associate to (`>=0`)\nvoid mi_heap_set_numa_affinity(mi_heap_t* heap, int numa_node);\n\n/// @brief __v3__: the main heap (of the current sub-process)\n/// @return a pointer to the main heap\n/// Every (sub)process has a main heap that cannot be deleted\n/// or destroyed until (sub)process exit. If other heaps are \n/// deleted their live objects are migrated to the main heap.\nmi_heap_t* mi_heap_main(void);\n\n/// @brief __v3__: return the heap that contains the given pointer.\n/// @param p Any pointer -- not required to be previously allocated by mimalloc.\n/// @return the heap that contains the given pointer, or `NULL` if\n/// the pointer does not point into mimalloc allocated memory.\n/// This is a constant time efficient function.\nmi_heap_t* mi_heap_of(const void* p);\n\n/// @brief __v3__: does a heap contain a specific pointer?\n/// @param heap The heap to query.\n/// @param p Any pointer -- not required to be previously allocated by mimalloc.\n/// This is a constant time efficient function.\nbool mi_heap_contains(const mi_heap_t* heap, const void* p);\n\n/// @brief __v3__: is a pointer pointing into mimalloc managed memory?\n/// @param p Any pointer -- not required to be previously allocated by mimalloc.\n/// This is a constant time efficient function.\nbool mi_any_heap_contains(const void* p);\n\n\n/// __v1__,__v2__: Is a pointer part of our heap?\n/// @param p The pointer to check.\n/// @returns \\a true if this is a pointer into our heap.\n/// This function is relatively fast.\n/// @see mi_any_heap_contains() for __v3__.\nbool mi_is_in_heap_region(const void* p);\n\n/// __v1__,__v2__: Check if any pointer is part of the default heap of this thread.\n/// @param p   Any pointer -- not required to be previously allocated by us.\n/// @returns \\a true if \\a p points to a block in default heap of this thread.\n/// Note: expensive function, linear in the pages in the heap.\n/// @see mi_any_heap_contains() for __v3__\nbool mi_check_owned(const void* p);\n\n/// __v1__,__v2__: Does a heap contain a pointer to a previously allocated block?\n/// @param heap The heap.\n/// @param p Pointer to a previously allocated block (in any heap)-- cannot be some\n///          random pointer!\n/// @returns \\a true if the block pointed to by \\a p is in the \\a heap.\n/// @see mi_heap_owned(), mi_heap_contains() for __v3__\nbool mi_heap_contains_block(mi_heap_t* heap, const void* p);\n\n/// __v1__,__v2__: Check safely if any pointer is part of a heap.\n/// @param heap The heap.\n/// @param p   Any pointer -- not required to be previously allocated by us.\n/// @returns \\a true if \\a p points to a block in \\a heap.\n/// Note: expensive function, linear in the pages in the heap.\n/// @see mi_heap_contains_block(), mi_heap_contains() for __v3__\nbool mi_heap_check_owned(mi_heap_t* heap, const void* p);\n\n/// Release outstanding resources in a specific heap.\nvoid mi_heap_collect(mi_heap_t* heap, bool force);\n\n/// Allocate in a specific heap.\n/// @see mi_malloc()\nvoid* mi_heap_malloc(mi_heap_t* heap, size_t size);\n\n/// Allocate a small object in a specific heap.\n/// \\a size must be smaller or equal to MI_SMALL_SIZE_MAX().\n/// @see mi_malloc()\nvoid* mi_heap_malloc_small(mi_heap_t* heap, size_t size);\n\n/// Allocate zero-initialized in a specific heap.\n/// @see mi_zalloc()\nvoid* mi_heap_zalloc(mi_heap_t* heap, size_t size);\n\n/// Allocate \\a count zero-initialized elements in a specific heap.\n/// @see mi_calloc()\nvoid* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size);\n\n/// Allocate \\a count elements in a specific heap.\n/// @see mi_mallocn()\nvoid* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size);\n\n/// Duplicate a string in a specific heap.\n/// @see mi_strdup()\nchar* mi_heap_strdup(mi_heap_t* heap, const char* s);\n\n/// Duplicate a string of at most length \\a n in a specific heap.\n/// @see mi_strndup()\nchar* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n);\n\n/// Resolve a file path name using a specific \\a heap to allocate the result.\n/// @see mi_realpath()\nchar* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name);\n\nvoid* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize);\nvoid* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size);\nvoid* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize);\n\nvoid* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment);\nvoid* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset);\nvoid* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment);\nvoid* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset);\nvoid* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment);\nvoid* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset);\nvoid* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment);\nvoid* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset);\n\n/// \\}\n\n\n\n/// \\defgroup analysis Heap Introspection\n/// Walk areas and blocks of the heap at runtime.\n///\n/// \\{\n\n\n/// An area of heap space contains blocks of a single size.\n/// The bytes in freed blocks are `committed - used`.\ntypedef struct mi_heap_area_s {\n  void*  blocks;      ///< start of the area containing heap blocks\n  size_t reserved;    ///< bytes reserved for this area\n  size_t committed;   ///< current committed bytes of this area\n  size_t used;        ///< bytes in use by allocated blocks\n  size_t block_size;  ///< size in bytes of one block\n  size_t full_block_size; ///< size in bytes of a full block including padding and metadata.\n  int    heap_tag;    ///< heap tag associated with this area (see \\a mi_heap_new_ex)\n} mi_heap_area_t;\n\n/// Visitor function passed to mi_heap_visit_blocks()\n/// @returns \\a true if ok, \\a false to stop visiting (i.e. break)\n///\n/// This function is always first called for every \\a area\n/// with \\a block as a \\a NULL pointer. If \\a visit_all_blocks\n/// was \\a true, the function is then called for every allocated\n/// block in that area.\ntypedef bool (mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg);\n\n/// Visit all areas and blocks in a heap.\n/// @param heap The heap to visit.\n/// @param visit_blocks If \\a true visits all allocated blocks, otherwise\n///                         \\a visitor is only called for every heap area.\n/// @param visitor This function is called for every area in the heap\n///                 (with \\a block as \\a NULL). If \\a visit_all_blocks is\n///                 \\a true, \\a visitor is also called for every allocated\n///                 block in every area (with `block!=NULL`).\n///                 return \\a false from this function to stop visiting early.\n/// @param arg Extra argument passed to \\a visitor.\n/// @returns \\a true if all areas and blocks were visited.\n///\n/// Note: requires the option mi_option_visit_abandoned() to be set\n/// at the start of the program to visit all abandoned blocks as well.\nbool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);\n\n/// @brief __v1__,__v2__: Visit all areas and blocks in abandoned heaps.\n/// @param subproc_id The sub-process id associated with the abandoned heaps.\n/// @param heap_tag Visit only abandoned memory with the specified heap tag, use -1 to visit all abandoned memory.\n/// @param visit_blocks If \\a true visits all allocated blocks, otherwise\n///                         \\a visitor is only called for every heap area.\n/// @param visitor This function is called for every area in the heap\n///                 (with \\a block as \\a NULL). If \\a visit_all_blocks is\n///                 \\a true, \\a visitor is also called for every allocated\n///                 block in every area (with `block!=NULL`).\n///                 return \\a false from this function to stop visiting early.\n/// @param arg extra argument passed to the \\a visitor.\n/// @return \\a true if all areas and blocks were visited.\n///\n/// Note: requires the option mi_option_visit_abandoned() to be set\n/// at the start of the program.\nbool mi_abandoned_visit_blocks(mi_subproc_id_t subproc_id, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);\n\n/// \\}\n\n// ------------------------------------------------------\n// Arenas\n// ------------------------------------------------------\n\n/// \\defgroup arenas Arenas\n/// Arenas are large memory areas (usually 1GiB+) from which mimalloc allocates memory.\n/// The arenas are usually allocated on-demand from the OS but can be reserved explicitly.\n/// It is also possible to give previously allocated memory to mimalloc to manage.\n/// Heaps can be associated with a specific arena to only allocate memory from that arena.\n///\n/// \\{\n\n/// Each arena has an associated identifier.\ntypedef int mi_arena_id_t;\n\n/// Reserve OS memory for use by mimalloc. Reserved areas are used\n/// before allocating from the OS again. By reserving a large area upfront,\n/// allocation can be more efficient, and can be better managed on systems\n/// without `mmap`/`VirtualAlloc` (like WASM for example).\n/// @param size        The size to reserve.\n/// @param commit      Commit the memory upfront.\n/// @param allow_large Allow large OS pages (2MiB) to be used?\n/// @return \\a 0 if successful, and an error code otherwise (e.g. `ENOMEM`).\nint  mi_reserve_os_memory(size_t size, bool commit, bool allow_large);\n\n/// @brief Reserve OS memory to be managed in an arena.\n/// @param size Size the reserve.\n/// @param commit Should the memory be initially committed?\n/// @param allow_large Allow the use of large OS pages?\n/// @param exclusive  Is the returned arena exclusive?\n/// @param arena_id The new arena identifier.\n/// @return Zero on success, an error code otherwise.\nint mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id);\n\n/// Reserve \\a pages of huge OS pages (1GiB) evenly divided over \\a numa_nodes nodes,\n/// but stops after at most `timeout_msecs` seconds.\n/// @param pages The number of 1GiB pages to reserve.\n/// @param numa_nodes The number of nodes do evenly divide the pages over, or 0 for using the actual number of NUMA nodes.\n/// @param timeout_msecs Maximum number of milli-seconds to try reserving, or 0 for no timeout.\n/// @returns 0 if successful, \\a ENOMEM if running out of memory, or \\a ETIMEDOUT if timed out.\n///\n/// The reserved memory is used by mimalloc to satisfy allocations.\n/// May quit before \\a timeout_msecs are expired if it estimates it will take more than\n/// 1.5 times \\a timeout_msecs. The time limit is needed because on some operating systems\n/// it can take a long time to reserve contiguous memory if the physical memory is\n/// fragmented.\nint mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs);\n\n/// Reserve \\a pages of huge OS pages (1GiB) at a specific \\a numa_node,\n/// but stops after at most `timeout_msecs` seconds.\n/// @param pages The number of 1GiB pages to reserve.\n/// @param numa_node The NUMA node where the memory is reserved (start at 0). Use -1 for no affinity.\n/// @param timeout_msecs Maximum number of milli-seconds to try reserving, or 0 for no timeout.\n/// @returns 0 if successful, \\a ENOMEM if running out of memory, or \\a ETIMEDOUT if timed out.\n///\n/// The reserved memory is used by mimalloc to satisfy allocations.\n/// May quit before \\a timeout_msecs are expired if it estimates it will take more than\n/// 1.5 times \\a timeout_msecs. The time limit is needed because on some operating systems\n/// it can take a long time to reserve contiguous memory if the physical memory is\n/// fragmented.\nint mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs);\n\n/// @brief Reserve huge OS pages (1GiB) into a single arena.\n/// @param pages             Number of 1GiB pages to reserve.\n/// @param numa_node         The associated NUMA node, or -1 for no NUMA preference.\n/// @param timeout_msecs     Max amount of milli-seconds this operation is allowed to take. (0 is infinite)\n/// @param exclusive         If exclusive, only a heap associated with this arena can allocate in it.\n/// @param arena_id          The arena identifier.\n/// @return 0 if successful, \\a ENOMEM if running out of memory, or \\a ETIMEDOUT if timed out.\nint   mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id);\n\n/// @brief Return the minimal alignment required for managed OS memory.\n/// @see mi_manage_os_memory(), mi_manage_os_memory_ex()\nsize_t mi_arena_min_alignment(void);\n\n/// Manage a particular memory area for use by mimalloc.\n/// This is just like `mi_reserve_os_memory` except that the area should already be\n/// allocated in some manner and available for use my mimalloc.\n/// @param start       Start of the memory area\n/// @param size        The size of the memory area.\n/// @param is_committed Is the area already committed?\n/// @param is_large    Does it consist of large OS pages? Set this to \\a true as well for memory\n///                    that should not be decommitted or protected (like rdma etc.)\n/// @param is_zero     Does the area consists of zero's?\n/// @param numa_node   Possible associated numa node or `-1`.\n/// @return \\a true if successful, and \\a false on error.\nbool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node);\n\n/// @brief Manage externally allocated memory as a mimalloc arena. This memory will not be freed by mimalloc.\n/// @param start Start address of the area.\n/// @param size  Size in bytes of the area.\n/// @param is_committed  Is the memory already committed?\n/// @param is_large      Does it consist of (pinned) large OS pages?\n/// @param is_zero       Is the memory zero-initialized?\n/// @param numa_node     Associated NUMA node, or -1 to have no NUMA preference.\n/// @param exclusive     Is the arena exclusive (where only heaps associated with the arena can allocate in it)\n/// @param arena_id      The new arena identifier.\n/// @return `true` if successful.\nbool  mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id);\n\n\n/// @brief Show all current arena's.\nvoid mi_debug_show_arenas(void);\n\n/// @brief  Return the start and size of an arena.\n/// @param arena_id  The arena identifier.\n/// @param size      Returned size in bytes of the (virtual) arena area.\n/// @return base address of the arena.\nvoid* mi_arena_area(mi_arena_id_t arena_id, size_t* size);\n\n\n\n/// @brief Create a new heap that only allocates in the specified arena.\n/// @param arena_id The arena identifier.\n/// @return The new heap or `NULL`.\nmi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id);\n\n/// @brief __v1__,__v2__: Create a new heap.\n/// @param heap_tag       The heap tag associated with this heap; heaps only reclaim memory between heaps with the same tag.\n/// @param allow_destroy  Is \\a mi_heap_destroy allowed?  Not allowing this allows the heap to reclaim memory from terminated threads.\n/// @param arena_id       If not 0, the heap will only allocate from the specified arena.\n/// @return A new heap or `NULL` on failure.\n///\n/// The \\a arena_id can be used by runtimes to allocate only in a specified pre-reserved arena.\n/// This is used for example for a compressed pointer heap in Koka.\n/// The \\a heap_tag enables heaps to keep objects of a certain type isolated to heaps with that tag.\n/// This is used for example in the CPython integration.\n/// @see mi_heap_new_in_arena() for __v3__\nmi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id);\n\n/// \\}\n\n\n/// \\defgroup subproc Subprocesses\n/// A sub-process contains its own arena's and heaps that are fully separate from the main (sub) process. \n///\n/// A thread can belong to only one subprocess at a time.\n/// This can be used to separate out logically separate parts\n/// of a program (like running multiple interpreter instances in CPython).\n///\n/// \\{\n\n/// A process can associate threads with sub-processes.\n/// Sub-processes are isolated and will not reclaim or visit memory \n/// from other sub-processes. \n/// Each subprocess always has an associated main heap.\ntypedef void* mi_subproc_id_t;\n\n/// @brief Get the main sub-process identifier.\nmi_subproc_id_t mi_subproc_main(void);\n\n/// @brief Get the current sub-process identifier of this thread.\nmi_subproc_id_t mi_subproc_current(void);\n\n/// @brief Create a fresh sub-process (with no associated threads yet).\n/// @return The new sub-process identifier.\nmi_subproc_id_t mi_subproc_new(void);\n\n/// @brief __v3__: Destroy a previously created sub-process.\n/// @param subproc The sub-process identifier.\n/// Only destroy sub-processes if all associated threads have terminated.\nvoid mi_subproc_destroy(mi_subproc_id_t subproc);\n\n/// @brief __v1__,__v2__: Delete a previously created sub-process.\n/// @param subproc The sub-process identifier.\n/// Only delete sub-processes if all associated threads have terminated.\n/// @see mi_subproc_destroy()\nvoid mi_subproc_delete(mi_subproc_id_t subproc);\n\n/// @brief Add the current thread to the given sub-process.\n/// This should be called right after a thread is created \n/// (and no allocation has taken place yet).\nvoid mi_subproc_add_current_thread(mi_subproc_id_t subproc);\n\n/// @brief The type of a heap visitor function.\n/// @return Return `true` to keep visiting. Returning `false` will\n/// stop visiting further heaps.\ntypedef bool (mi_heap_visit_fun)(mi_heap_t* heap, void* arg);\n\n/// @brief Visit all heaps belonging to a subprocess.\n/// @param subproc The subprocess that the heaps should belong to\n/// @param visitor The visitor function is called with each heap in `subproc`\n/// @param arg A user argument that is passed to each visitor function call.\n/// Return `true` if all heaps are visited (and false otherwise).\nbool mi_subproc_visit_heaps(mi_subproc_id_t subproc, mi_heap_visit_fun* visitor, void* arg);\n\n/// \\}\n\n\n// ------------------------------------------------------\n// Extended functionality\n// ------------------------------------------------------\n\n/// \\defgroup extended Extended Functions\n/// Internal functionality.\n///\n/// \\{\n\n/// Maximum size allowed for small allocations in\n/// #mi_malloc_small and #mi_zalloc_small (usually `128*sizeof(void*)` (= 1KB on 64-bit systems))\n#define MI_SMALL_SIZE_MAX   (128*sizeof(void*))\n\n/// @brief Return the mimalloc version.\n/// @return The version. For v1,v2 the version is 100*major + 10*minor + patch (for example 227 for v2.2.7).\n/// For __v3__, it is 1000*major + 100*minor + path (for example, 3207 for v3.2.7).\nint  mi_version(void);\n\n/// Eagerly free memory.\n/// @param force If \\a true, aggressively return memory to the OS (can be expensive!)\n///\n/// Regular code should not have to call this function. It can be beneficial\n/// in very narrow circumstances; in particular, when a long running thread\n/// allocates a lot of blocks that are freed by other threads it may improve\n/// resource usage by calling this every once in a while.\nvoid mi_collect(bool force);\n\n/// __v3__: Communicate that a thread is in a threadpool. \n/// This is done automatically for threads in the Windows threadpool,\n/// but if using a custom threadpool it is good to call this on worker threads.\n/// Internally, mimalloc uses different locality heuristics for worker threads\n/// to try to reduce non-local accesss.\nvoid mi_thread_set_in_threadpool(void);\n\n/// Is the C runtime \\a malloc API redirected?\n/// @returns \\a true if all malloc API calls are redirected to mimalloc.\n///\n/// Currently only used on Windows.\nbool mi_is_redirected();\n\n/// Initialize mimalloc on a thread.\n/// Should not be used as on most systems (pthreads, windows) this is done\n/// automatically.\nvoid mi_thread_init(void);\n\n/// Uninitialize mimalloc on a thread.\n/// Should not be used as on most systems (pthreads, windows) this is done\n/// automatically. Ensures that any memory that is not freed yet (but will\n/// be freed by other threads in the future) is properly handled.\nvoid mi_thread_done(void);\n\n/// Type of deferred free functions.\n/// @param force If \\a true all outstanding items should be freed.\n/// @param heartbeat A monotonically increasing count.\n/// @param arg Argument that was passed at registration to hold extra state.\n///\n/// @see mi_register_deferred_free\ntypedef void (mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg);\n\n/// Register a deferred free function.\n/// @param deferred_free Address of a deferred free-ing function or \\a NULL to unregister.\n/// @param arg Argument that will be passed on to the deferred free function.\n///\n/// Some runtime systems use deferred free-ing, for example when using\n/// reference counting to limit the worst case free time.\n/// Such systems can register (re-entrant) deferred free function\n/// to free more memory on demand. When the \\a force parameter is\n/// \\a true all possible memory should be freed.\n/// The per-thread \\a heartbeat parameter is monotonically increasing\n/// and guaranteed to be deterministic if the program allocates\n/// deterministically. The \\a deferred_free function is guaranteed\n/// to be called deterministically after some number of allocations\n/// (regardless of freeing or available free memory).\n/// At most one \\a deferred_free function can be active.\nvoid   mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg);\n\n/// Type of output functions.\n/// @param msg Message to output.\n/// @param arg Argument that was passed at registration to hold extra state.\n///\n/// @see mi_register_output()\ntypedef void (mi_output_fun)(const char* msg, void* arg);\n\n/// Register an output function.\n/// @param out The output function, use `NULL` to output to stderr.\n/// @param arg Argument that will be passed on to the output function.\n///\n/// The `out` function is called to output any information from mimalloc,\n/// like verbose or warning messages.\nvoid mi_register_output(mi_output_fun* out, void* arg);\n\n/// Type of error callback functions.\n/// @param err Error code (see mi_register_error() for a complete list).\n/// @param arg Argument that was passed at registration to hold extra state.\n///\n/// @see mi_register_error()\ntypedef void (mi_error_fun)(int err, void* arg);\n\n/// Register an error callback function.\n/// @param errfun The error function that is called on an error (use \\a NULL for default)\n/// @param arg Extra argument that will be passed on to the error function.\n///\n/// The \\a errfun function is called on an error in mimalloc after emitting\n/// an error message (through the output function). It as always legal to just\n/// return from the \\a errfun function in which case allocation functions generally\n/// return \\a NULL or ignore the condition. The default function only calls abort()\n/// when compiled in secure mode with an \\a EFAULT error. The possible error\n/// codes are:\n/// * \\a EAGAIN: Double free was detected (only in debug and secure mode).\n/// * \\a EFAULT: Corrupted free list or meta-data was detected (only in debug and secure mode).\n/// * \\a ENOMEM: Not enough memory available to satisfy the request.\n/// * \\a EOVERFLOW: Too large a request, for example in mi_calloc(), the \\a count and \\a size parameters are too large.\n/// * \\a EINVAL: Trying to free or re-allocate an invalid pointer.\nvoid mi_register_error(mi_error_fun* errfun, void* arg);\n\n/// Allocate a small object.\n/// @param size The size in bytes, can be at most #MI_SMALL_SIZE_MAX.\n/// @returns a pointer to newly allocated memory of at least \\a size\n/// bytes, or \\a NULL if out of memory.\n/// This function is meant for use in run-time systems for best\n/// performance and does not check if \\a size was indeed small -- use\n/// with care!\nvoid* mi_malloc_small(size_t size);\n\n/// Allocate a zero initialized small object.\n/// @param size The size in bytes, can be at most #MI_SMALL_SIZE_MAX.\n/// @returns a pointer to newly allocated zero-initialized memory of at\n/// least \\a size bytes, or \\a NULL if out of memory.\n/// This function is meant for use in run-time systems for best\n/// performance and does not check if \\a size was indeed small -- use\n/// with care!\nvoid* mi_zalloc_small(size_t size);\n\n/// \\}\n\n\n// ------------------------------------------------------\n// Statistics\n// ------------------------------------------------------\n\n/// \\defgroup stats Statistics\n/// Print out allocation statistics.\n/// \\{\n\n/// Statistics version. Increased on each backward incompatible change.\n#define MI_STAT_VERSION   4\n\n/// Helper to declare a properly initialized mi_stats_t() local variable as \\a name\n/// where the \\a size and \\a version fields are properly initialized.\n/// For example:\n/// ```\n/// mi_stats_t_decl(stats);\n/// if (mi_stats_get(&stats)) {\n///   ...\n/// }\n/// ```\n#define mi_stats_t_decl(name)\n\n/// Statistics. See [include/mimalloc-stats.h](https://github.com/microsoft/mimalloc/blob/main/include/mimalloc-stats.h) for the full definition.\nstruct mi_stats_s {\n  /// initialize with `sizeof(mi_stats_t)` (so linking dynamically with a separately compiled mimalloc is safe)\n  size_t size;      \n  /// initialize with `MI_STAT_VERSION (so linking dynamically with a separately compiled mimalloc is safe)\n  size_t version;  \n};\n\n/// Statistics. See [include/mimalloc-stats.h](https://github.com/microsoft/mimalloc/blob/main/include/mimalloc-stats.h) for the full definition.\ntypedef struct mi_stats_s mi_stats_t;\n\n\n/// @brief Print out all process info.\n/// @see mi_process_info()\nvoid mi_process_info_print(void);\n\n/// @brief Print out all process info.\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\n/// @see mi_process_print()\nvoid mi_process_info_print_out(mi_output_fun* out, void* arg);\n\n/// Return process information (time and memory usage).\n/// @param elapsed_msecs   Optional. Elapsed wall-clock time of the process in milli-seconds.\n/// @param user_msecs      Optional. User time in milli-seconds (as the sum over all threads).\n/// @param system_msecs    Optional. System time in milli-seconds.\n/// @param current_rss     Optional. Current working set size (touched pages).\n/// @param peak_rss        Optional. Peak working set size (touched pages).\n/// @param current_commit  Optional. Current committed memory (backed by the page file).\n/// @param peak_commit     Optional. Peak committed memory (backed by the page file).\n/// @param page_faults     Optional. Count of hard page faults.\n///\n/// The \\a current_rss is precise on Windows and MacOSX; other systems estimate\n/// this using \\a current_commit. The \\a commit is precise on Windows but estimated\n/// on other systems as the amount of read/write accessible memory reserved by mimalloc.\nvoid mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults);\n\n\n/// Deprecated\n/// @param out Ignored, outputs to the registered output function or stderr by default.\n///\n/// Most detailed when using a debug build.\nvoid mi_stats_print(void* out);\n\n/// Print statistics of the current subprocess aggregated over all its heaps.\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\n///\n/// Most detailed when using a debug build.\nvoid mi_stats_print_out(mi_output_fun* out, void* arg);\n\n/// @brief Get the statistics for the current subprocess aggregated over all its heaps.\n/// @param stats Pointer to a mi_stats_t() structure (declared as `mi_stats_t_decl(name)`).\n/// @return \\a true if \\a stats is not \\a NULL, and if \\a stats->size \n/// and \\a stats->version match the `sizeof(mi_stats_t)` and `MI_STAT_VERSION`\n/// with the linked mimalloc version.\nbool mi_stats_get(mi_stats_t* stats);\n\n/// @brief Get the statistics for the current subprocess aggregated over all its heaps as JSON.\n/// @param buf_size Byte size of the buffer \\a buf (or 0 if \\a buf is \\a NULL)\n/// @param buf The buffer. Pass \\a NULL to allocate a fresh buffer.\n/// @return Pointer to the buffer or \\a NULL on failure.\n/// Use mi_free() to free the buffer if the \\a buf parameter was \\a NULL.\nchar*  mi_stats_get_json(size_t buf_size, char* buf);\n\n/// @brief __v3__: Return the block size for the given bin.\nsize_t  mi_stats_get_bin_size(size_t bin);\n\n/// @brief __v3__: Return statistics for a given heap.\n/// @param heap The heap.\n/// @param stats Pointer to a mi_stats_t() structure (declared as `mi_stats_t_decl(name)`).\n/// @return \\a true if \\a stats is not \\a NULL, and if \\a stats->size \n/// and \\a stats->version match the `sizeof(mi_stats_t)` and `MI_STAT_VERSION`\n/// with the linked mimalloc version.\nbool mi_heap_stats_get(mi_heap_t* heap, mi_stats_t* stats);\n\n/// @brief __v3__: Get the statistics for a heap as JSON.\n/// @param heap The heap.\n/// @param buf_size Byte size of the buffer \\a buf (or 0 if \\a buf is \\a NULL).\n/// @param buf The buffer. Pass \\a NULL to allocate a fresh buffer.\n/// @return Pointer to the buffer or \\a NULL on failure.\n/// Use mi_free() to free the buffer if the \\a buf parameter was \\a NULL.\nchar* mi_heap_stats_get_json(mi_heap_t* heap, size_t buf_size, char* buf);      // use mi_free to free the result if the input buf == NULL\n\n/// @brief __v3__: Show the heap statistics as JSON.\n/// @param heap The heap.\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\nvoid mi_heap_stats_print_out(mi_heap_t* heap, mi_output_fun* out, void* arg);\n\n/// @brief __v3__: xplicitly merge the statistics of the current heap with the subprocess.\n/// @param heap The heap.\n/// After this call, the heap statistics are reset.\nvoid mi_heap_stats_merge_to_subproc(mi_heap_t* heap);\n\n/// @brief __v3__: Get statistics for a given subprocess aggregated over all its heaps.\n/// @param subproc_id The subprocess\n/// @param stats Pointer to a mi_stats_t() structure (declared as `mi_stats_t_decl(name)`).\n/// @return \\a true if \\a stats is not \\a NULL, and if \\a stats->size \n/// and \\a stats->version match the `sizeof(mi_stats_t)` and `MI_STAT_VERSION`\n/// with the linked mimalloc version.\nbool mi_subproc_stats_get(mi_subproc_id_t subproc_id, mi_stats_t* stats);\n\n/// @brief __v3__: Show the subproc statistics aggregated over all its heaps as JSON.\n/// @param subproc_id The subprocess.\n/// @param buf_size Byte size of the buffer \\a buf (or 0 if \\a buf is \\a NULL).\n/// @param buf The buffer. Pass \\a NULL to allocate a fresh buffer.\n/// @return Pointer to the buffer or \\a NULL on failure.\n/// Use mi_free() to free the buffer if the \\a buf parameter was \\a NULL.\nchar* mi_subproc_stats_get_json(mi_subproc_id_t subproc_id, size_t buf_size, char* buf);      // use mi_free to free the result if the input buf == NULL\n\n/// @brief __v3__: Print the subproc statistics aggregated over all its heaps.\n/// @param subproc_id The subprocess.\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\nvoid  mi_subproc_stats_print_out(mi_subproc_id_t subproc_id, mi_output_fun* out, void* arg);\n\n\n/// @brief __v3__: Print statistics for a given subprocess with each heap separately printed.\n/// @param subproc_id The subprocess\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\nvoid mi_subproc_heap_stats_print_out(mi_subproc_id_t subproc_id, mi_output_fun* out, void* arg);\n\n/// @brief __v3__: Show the given statistics as JSON.\n/// @param stats The statistics.\n/// @param buf_size Byte size of the buffer \\a buf (or 0 if \\a buf is \\a NULL).\n/// @param buf The buffer. Pass \\a NULL to allocate a fresh buffer.\n/// @return Pointer to the buffer or \\a NULL on failure.\n/// Use mi_free() to free the buffer if the \\a buf parameter was \\a NULL.\nchar*  mi_stats_as_json( mi_stats_t* stats, size_t buf_size, char* buf);\n\n/// __v1__,__v2__: Reset statistics.\nvoid mi_stats_reset(void);\n\n/// __v1__,__v2__: Merge thread local statistics with the main statistics and reset.\nvoid mi_stats_merge(void);\n\n/// \\}\n\n// ------------------------------------------------------\n// Runtime Options\n// ------------------------------------------------------\n\n/// \\defgroup options Runtime Options\n/// Set runtime parameters.\n///\n/// \\{\n\n/// @brief Print out all runtime parameters for mimalloc.\n/// Also printed with `MIMALLOC_VERBOSE=1` at startup.\nvoid mi_options_print(void);\n\n/// @brief Print out all process info.\n/// @param out An output function or \\a NULL for the default.\n/// @param arg Optional argument passed to \\a out (if not \\a NULL)\n/// Also printed with `MIMALLOC_VERBOSE=1` at startup.\n/// @see mi_options_print()\nvoid mi_options_print_out(mi_output_fun* out, void* arg);\n\n\n/// Runtime options.\ntypedef enum mi_option_e {\n  // stable options\n  mi_option_show_errors,  ///< Print error messages.\n  mi_option_show_stats,   ///< Print statistics on termination.\n  mi_option_verbose,      ///< Print verbose messages.\n  mi_option_max_errors,                 ///< issue at most N error messages\n  mi_option_max_warnings,               ///< issue at most N warning messages\n\n  // advanced options\n  mi_option_reserve_huge_os_pages,    ///< reserve N huge OS pages (1GiB pages) at startup\n  mi_option_reserve_huge_os_pages_at, ///< Reserve N huge OS pages at a specific NUMA node N.\n  mi_option_reserve_os_memory,        ///< reserve specified amount of OS memory in an arena at startup (internally, this value is in KiB; use mi_option_get_size())\n  mi_option_allow_large_os_pages,     ///< allow large (2 or 4 MiB) OS pages, implies eager commit. \n  mi_option_purge_decommits,          ///< should a memory purge decommit? (=1). Set to 0 to use memory reset on a purge (instead of decommit)\n  mi_option_arena_reserve,            ///< initial memory size for arena reservation (= 1 GiB on 64-bit) (internally, this value is in KiB; use mi_option_get_size())\n  mi_option_os_tag,                   ///< tag used for OS logging (macOS only for now) (=100)\n  mi_option_retry_on_oom,             ///< retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows)\n  mi_option_generic_collect,          ///< collect heaps every N (=10000) generic allocation calls\n  mi_option_allow_thp,                ///< allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process.\n\n  // guard pages\n  mi_option_guarded_min,              ///< only used when building with MI_GUARDED: minimal rounded object size for guarded objects (=0)\n  mi_option_guarded_max,              ///< only used when building with MI_GUARDED: maximal rounded object size for guarded objects (=0)\n  mi_option_guarded_precise,          ///< disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)\n  mi_option_guarded_sample_rate,      ///< 1 out of N allocations in the min/max range will be guarded (=1000)\n  mi_option_guarded_sample_seed,      ///< can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0)\n\n  // experimental options\n  mi_option_eager_commit,             ///< __v1__,__v2__: eager commit segments? (after `eager_commit_delay` segments) (enabled by default).\n  mi_option_eager_commit_delay,       ///< __v2__: the first N segments per thread are not eagerly committed (but per page in the segment on demand)\n  mi_option_arena_eager_commit,       ///< eager commit arenas? Use 2 to enable just on overcommit systems (=2)\n  mi_option_abandoned_page_purge,     ///< __v1__,__v2__: immediately purge delayed purges on thread termination\n  mi_option_purge_delay,              ///< memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all. (=10)\n  mi_option_use_numa_nodes,           ///< 0 = use all available numa nodes, otherwise use at most N nodes.\n  mi_option_disallow_os_alloc,        ///< 1 = do not use OS memory for allocation (but only programmatically reserved arenas)  \n  mi_option_max_segment_reclaim,        ///< __v2__: max. percentage of the abandoned segments can be reclaimed per try (=10%)\n  mi_option_destroy_on_exit,            ///< if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe\n  mi_option_arena_purge_mult,           ///< multiplier for `purge_delay` for the purging delay for arenas (=10)\n  mi_option_abandoned_reclaim_on_free,  ///< __v1__,__v2__: allow to reclaim an abandoned segment on a free (=1)\n  mi_option_purge_extend_delay,         ///< __v1__,__v2__: extend purge delay on each subsequent delay (=1)\n  mi_option_disallow_arena_alloc,       ///< 1 = do not use arena's for allocation (except if using specific arena id's)\n  mi_option_visit_abandoned,            ///< allow visiting heap blocks from abandoned threads (=0)\n  mi_option_target_segments_per_thread, ///< __v1__,__v2__: experimental (=0)\n\n  // v3 options\n  mi_option_page_reclaim_on_free,       ///< __v3__: reclaim abandoned pages on a free (=0). -1 disallowr always, 0 allows if the page originated from the current theap, 1 allow always\n  mi_option_page_full_retain,           ///< __v3__: retain N full (small) pages per size class (=2). Use -1 for infinite (as in __v1__,__v2__).\n  mi_option_page_max_candidates,        ///< __v3__: max candidate pages to consider for allocation (=4)\n  mi_option_max_vabits,                 ///< __v3__: max user space virtual address bits to consider (=48)\n  mi_option_pagemap_commit,             ///< __v3__: commit the full pagemap (to always catch invalid pointer uses) (=0)\n  mi_option_page_commit_on_demand,      ///< __v3__: commit page memory on-demand (=0)\n  mi_option_page_max_reclaim,           ///< __v3__: don't reclaim pages of the same originating theap if we already own N pages (in that size class) (=-1 (unlimited))\n  mi_option_page_cross_thread_max_reclaim, ///< __v3__: don't reclaim pages across threads if we already own N pages (in that size class) (=16)\n  mi_option_minimal_purge_size,         ///< __v3__: set minimal purge size (in KiB) (=0). By default set to either 64 or 2048 if THP is enabled. (internal value is in KiB so use mi_option_get_size())\n  mi_option_arena_max_object_size,      ///< __v3__: set maximal object size that can be allocated in an arena (in KiB) (=2GiB on 64-bit). \n  \n  _mi_option_last\n} mi_option_t;\n\n\nbool  mi_option_is_enabled(mi_option_t option);\nvoid  mi_option_enable(mi_option_t option);\nvoid  mi_option_disable(mi_option_t option);\nvoid  mi_option_set_enabled(mi_option_t option, bool enable);\nvoid  mi_option_set_enabled_default(mi_option_t option, bool enable);\n\nlong   mi_option_get(mi_option_t option);\nlong   mi_option_get_clamp(mi_option_t option, long min, long max);\nsize_t mi_option_get_size(mi_option_t option);\n\nvoid  mi_option_set(mi_option_t option, long value);\nvoid  mi_option_set_default(mi_option_t option, long value);\n\n/// \\}\n\n/// \\defgroup theap Thread-local heaps\n/// __v3__: Thread local heaps.\n///\n/// The use of thread-local heaps is discouraged and only recommended\n/// for special cases like runtime systems that keep their own thread-local\n/// state.\n/// The \"theaps\" are thread-local heaps that __v3__ uses internally\n/// to implement efficient first-class heaps. \n/// \n/// __v1__,__v2__: the `mi_heap_t` heaps in v1/v2 are exactly these `mi_theap_t`\n/// theaps in v3. \n/// \\{\n\n/// Type of thread-local heaps.\nstruct mi_theap_s;\n\n/// Type of thread-local heaps.\ntypedef struct mi_theap_s mi_theap_t;\n\n/// @brief Return the thread-local theap for the given heap.\n/// @param heap The owning heap. \n/// @return The theap that serves thread-local allocations for the given `heap`.\n/// This can be used by runtime systems to request the thread-local theap\n/// once and then use it in its allocation functions (like mi_theap_malloc() or mi_theap_malloc_small() ) to avoid re-looking up\n/// the theap at each allocation call.\nmi_theap_t* mi_heap_theap(mi_heap_t* heap);\n\n/// Release outstanding resources in a specific theap.\nvoid mi_theap_collect(mi_theap_t* theap, bool force);\n\n/// Set the default thread-local theap to use in the current thread for mi_malloc() et al.\n/// @param theap  The new default theap.\n/// @returns The previous default theap.\n/// By default it will point to the theap belonging to the main heap (mi_heap_main()).\nmi_theap_t* mi_theap_set_default(mi_theap_t* theap);\n\n/// Get the default theap that is used for mi_malloc() et al. for the current thread.\n/// @returns The current default theap.\nmi_theap_t* mi_theap_get_default();\n\n\nvoid* mi_theap_malloc(mi_theap_t* theap, size_t size);\nvoid* mi_theap_zalloc(mi_theap_t* theap, size_t size);\nvoid* mi_theap_calloc(mi_theap_t* theap, size_t count, size_t size);\nvoid* mi_theap_malloc_small(mi_theap_t* theap, size_t size);\nvoid* mi_theap_malloc_aligned(mi_theap_t* theap, size_t size, size_t alignment);\nvoid* mi_theap_realloc(mi_theap_t* theap, void* p, size_t newsize);\n\n/// \\}\n\n\n/// \\defgroup posix Posix\n///  `mi_` prefixed implementations of various Posix, Unix, and C++ allocation functions.\n///  Defined for convenience as all redirect to the regular mimalloc API.\n///\n/// \\{\n\n/// Just as `free` but also checks if the pointer `p` belongs to our heap.\nvoid   mi_cfree(void* p);\nvoid* mi__expand(void* p, size_t newsize);\n\nsize_t mi_malloc_size(const void* p);\nsize_t mi_malloc_good_size(size_t size);\nsize_t mi_malloc_usable_size(const void *p);\n\nint mi_posix_memalign(void** p, size_t alignment, size_t size);\nint mi__posix_memalign(void** p, size_t alignment, size_t size);\nvoid* mi_memalign(size_t alignment, size_t size);\nvoid* mi_valloc(size_t size);\nvoid* mi_pvalloc(size_t size);\nvoid* mi_aligned_alloc(size_t alignment, size_t size);\n\nunsigned short* mi_wcsdup(const unsigned short* s);\nunsigned char*  mi_mbsdup(const unsigned char* s);\nint mi_dupenv_s(char** buf, size_t* size, const char* name);\nint mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name);\n\n/// Correspond s to [reallocarray](https://www.freebsd.org/cgi/man.cgi?query=reallocarray&sektion=3&manpath=freebsd-release-ports)\n/// in FreeBSD.\nvoid* mi_reallocarray(void* p, size_t count, size_t size);\n\n/// Corresponds to [reallocarr](https://man.netbsd.org/reallocarr.3) in NetBSD.\nint   mi_reallocarr(void* p, size_t count, size_t size);\n\nvoid* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment);\nvoid* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset);\n\nvoid mi_free_size(void* p, size_t size);\nvoid mi_free_size_aligned(void* p, size_t size, size_t alignment);\nvoid mi_free_aligned(void* p, size_t alignment);\n\n/// \\}\n\n/// \\defgroup cpp C++ wrappers\n/// `mi_` prefixed implementations of various C++ allocation functions.\n///\n///  These use C++ semantics on out-of-memory, generally calling\n///  `std::get_new_handler` and raising a `std::bad_alloc` exception on failure.\n///\n///  Note: use the `mimalloc-new-delete.h` header to override the \\a new\n///        and \\a delete operators globally. The wrappers here are mostly\n///        for convenience for library writers that need to interface with\n///        mimalloc from C++.\n///\n/// \\{\n\n/// like mi_malloc(), but when out of memory, use `std::get_new_handler` and raise `std::bad_alloc` exception on failure.\nvoid* mi_new(std::size_t n) noexcept(false);\n\n/// like mi_mallocn(), but when out of memory, use `std::get_new_handler` and raise `std::bad_alloc` exception on failure.\nvoid* mi_new_n(size_t count, size_t size) noexcept(false);\n\n/// like mi_malloc_aligned(), but when out of memory, use `std::get_new_handler` and raise `std::bad_alloc` exception on failure.\nvoid* mi_new_aligned(std::size_t n, std::align_val_t alignment) noexcept(false);\n\n/// like `mi_malloc`, but when out of memory, use `std::get_new_handler` but return \\a NULL on failure.\nvoid* mi_new_nothrow(size_t n);\n\n/// like `mi_malloc_aligned`, but when out of memory, use `std::get_new_handler` but return \\a NULL on failure.\nvoid* mi_new_aligned_nothrow(size_t n, size_t alignment);\n\n/// like mi_realloc(), but when out of memory, use `std::get_new_handler` and raise `std::bad_alloc` exception on failure.\nvoid* mi_new_realloc(void* p, size_t newsize);\n\n/// like mi_reallocn(), but when out of memory, use `std::get_new_handler` and raise `std::bad_alloc` exception on failure.\nvoid* mi_new_reallocn(void* p, size_t newcount, size_t size);\n\n/// \\a std::allocator implementation for mimalloc for use in STL containers.\n/// For example:\n/// ```\n/// std::vector<int, mi_stl_allocator<int> > vec;\n/// vec.push_back(1);\n/// vec.pop_back();\n/// ```\ntemplate<class T> struct mi_stl_allocator { }\n\n/// \\}\n\n/*! \\page build Building\n\nCheckout the sources from GitHub:\n```\ngit clone https://github.com/microsoft/mimalloc\n```\n\n## Windows\n\nOpen `ide/vs2022/mimalloc.sln` in Visual Studio 2022 and build.\nThe `mimalloc-lib` project builds a static library (in `out/msvc-x64`), while the\n`mimalloc-override-dll` project builds a DLL for overriding malloc\nin the entire program.\n\n## Linux, macOS, BSD, etc.\n\nWe use [`cmake`](https://cmake.org) as the build system:\n\n```\n> mkdir -p out/release\n> cd out/release\n> cmake ../..\n> make\n```\nThis builds the library as a shared (dynamic)\nlibrary (`.so` or `.dylib`), a static library (`.a`), and\nas a single object file (`.o`).\n\n`> sudo make install` (install the library and header files in `/usr/local/lib`  and `/usr/local/include`)\n\nYou can build the debug version which does many internal checks and\nmaintains detailed statistics as:\n\n```\n> mkdir -p out/debug\n> cd out/debug\n> cmake -DCMAKE_BUILD_TYPE=Debug ../..\n> make\n```\n\nThis will name the shared library as `libmimalloc-debug.so`.\n\nFinally, you can build a _secure_ version that uses guard pages, encrypted free lists, etc., as:\n\n```\n> mkdir -p out/secure\n> cd out/secure\n> cmake -DMI_SECURE=ON ../..\n> make\n```\n\nThis will name the shared library as `libmimalloc-secure.so`.\nUse `cmake ../.. -LH` to see all the available build options.\n\nThe examples use the default compiler. If you like to use another, use:\n\n```\n> CC=clang CXX=clang++ cmake ../..\n```\n\n## Cmake with Visual Studio\n\nYou can also use cmake on Windows. Open a Visual Studio 2022 development prompt \nand invoke `cmake` with the right [generator](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html) \nand architecture, like:\n\n```\n> cmake ..\\.. -G \"Visual Studio 17 2022\" -A x64 -DMI_OVERRIDE=ON\n```\n\nThe cmake build type is specified when actually building, for example:\n\n```\n> cmake --build . --config=Release\n```\n\nYou can also install the [LLVM toolset](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-170#install-1) \non Windows to build with the `clang-cl` compiler directly:\n\n```\n> cmake ../.. -G \"Visual Studio 17 2022\" -T ClangCl\n```\n\n\n## Single Source\n\nYou can also directly build the single `src/static.c` file as part of your project without\nneeding `cmake` at all. Make sure to also add the mimalloc `include` directory to the include path.\n\n*/\n\n/*! \\page using Using the Library\n\n### Build\n\nThe preferred usage is including `<mimalloc.h>`, linking with\nthe shared- or static library, and using the `mi_malloc` API exclusively for allocation. For example,\n```\ngcc -o myprogram -lmimalloc myfile.c\n```\n\nmimalloc uses only safe OS calls (`mmap` and `VirtualAlloc`) and can co-exist\nwith other allocators linked to the same program.\nIf you use `cmake`, you can simply use:\n```\nfind_package(mimalloc 2.1 REQUIRED)\n```\nin your `CMakeLists.txt` to find a locally installed mimalloc. Then use either:\n```\ntarget_link_libraries(myapp PUBLIC mimalloc)\n```\nto link with the shared (dynamic) library, or:\n```\ntarget_link_libraries(myapp PUBLIC mimalloc-static)\n```\nto link with the static library. See `test\\CMakeLists.txt` for an example.\n\n### C++\nFor best performance in C++ programs, it is also recommended to override the\nglobal `new` and `delete` operators. For convenience, mimalloc provides\n[`mimalloc-new-delete.h`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc-new-delete.h) which does this for you -- just include it in a single(!) source file in your project.\n\nIn C++, mimalloc also provides the `mi_stl_allocator` struct which implements the `std::allocator`\ninterface. For example:\n```\nstd::vector<some_struct, mi_stl_allocator<some_struct>> vec;\nvec.push_back(some_struct());\n```\n\n### Statistics\n\nYou can pass environment variables to print verbose messages (`MIMALLOC_VERBOSE=1`)\nand statistics (`MIMALLOC_SHOW_STATS=1`) (in the debug version):\n```\n> env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363\n\n175451865205073170563711388363 = 374456281610909315237213 * 468551\n\nsubproc 0\n blocks          peak       total     current       block      total#\n  bin S    4:    75.3 KiB    55.2 MiB     0          32   B       1.8 M    ok\n  bin S    6:    31.0 KiB   180.4 KiB     0          48   B       3.8 K    ok\n  bin S    8:    64   B      64   B       0          64   B       1        ok\n  bin S    9:   160   B     160   B       0          80   B       2        ok\n  bin S   17:     1.2 KiB     1.2 KiB     0         320   B       4        ok\n  bin S   21:   640   B       3.1 KiB     0         640   B       5        ok\n  bin S   33:     5.0 KiB     5.0 KiB     0           5.0 KiB     1        ok\n\n  binned    :    84.2 Ki     41.5 Mi      0                                ok\n  huge      :     0           0           0                                ok\n  total     :    84.2 KiB    41.5 MiB     0\n  malloc req:                29.7 MiB\n\n pages           peak       total     current       block      total#\n  touched   :   152.8 KiB   152.8 KiB   152.8 KiB\n  pages     :     8          14           0                                ok\n  abandoned :     1         249           0                                ok\n  reclaima  :     0\n  reclaimf  :   249\n  reabandon :     0\n  waits     :     0\n  extended  :    38\n  retire    :    35\n  searches  :     0.7 avg\n\n arenas          peak       total     current       block      total#\n  reserved  :     1.0 GiB     1.0 GiB     1.0 GiB\n  committed :     4.8 MiB     4.8 MiB     4.4 MiB\n  reset     :     0\n  purged    :   385.5 Ki\n  arenas    :     1\n  rollback  :     0\n  mmaps     :     3\n  commits   :     0\n  resets    :     1\n  purges    :     2\n  guarded   :     0\n  heaps     :     1           1           1\n\n process         peak       total     current       block      total#\n  threads   :     1           1           1\n  numa nodes:     1\n  elapsed   :     0.553 s\n  process   : user: 0.557 s, system: 0.013 s, faults: 29, peak rss: 2.1 MiB, peak commit: 4.8 MiB\n```\n\nThe above model of using the `mi_` prefixed API is not always possible\nthough in existing programs that already use the standard malloc interface,\nand another option is to override the standard malloc interface\ncompletely and redirect all calls to the _mimalloc_ library instead.\n\nSee \\ref overrides for more info.\n\n*/\n\n/*! \\page environment Environment Options\n\nYou can set further options either programmatically (using mi_option_set(), see \\ref options), or via environment variables:\n\n- `MIMALLOC_SHOW_STATS=1`: show statistics when the program terminates.\n- `MIMALLOC_VERBOSE=1`: show verbose messages (including statistics).\n- `MIMALLOC_SHOW_ERRORS=1`: show error and warning messages.\n\nAdvanced options:\n\n- `MIMALLOC_ARENA_EAGER_COMMIT=2`: turns on eager commit for the large arenas (usually 1GiB) from which mimalloc\n   allocates segments and pages. Set this to 2 (default) to\n   only enable this on overcommit systems (e.g. Linux). Set this to 1 to enable explicitly on other systems\n   as well (like Windows or macOS) which may improve performance (as the whole arena is committed at once).\n   Note that eager commit only increases the commit but not the actual the peak resident set\n   (rss) so it is generally ok to enable this.\n- `MIMALLOC_PURGE_DELAY=N`: the delay in `N` milli-seconds (by default `1000` in v3) after which mimalloc will purge\n   OS pages that are not in use. This signals to the OS that the underlying physical memory can be reused which\n   can reduce memory fragmentation especially in long running (server) programs. Setting `N` to `0` purges immediately when\n   a page becomes unused which can improve memory usage but also decreases performance.\n   Setting it to `-1` disables purging completely.\n- `MIMALLOC_PURGE_DECOMMITS=1`: By default \"purging\" memory means unused memory is decommitted (`MEM_DECOMMIT` on Windows,\n   `MADV_DONTNEED` (which decresease rss immediately) on `mmap` systems). Set this to 0 to instead \"reset\" unused\n   memory on a purge (`MEM_RESET` on Windows, generally `MADV_FREE` (which does not decrease rss immediately) on `mmap` systems).\n   Mimalloc generally does not \"free\" OS memory but only \"purges\" OS memory, in other words, it tries to keep virtual\n   address ranges and decommits within those ranges (to make the underlying physical memory available to other processes).\n\nFurther options for large workloads and services:\n\n- `MIMALLOC_ALLOW_THP=1`: By default always allow transparent huge pages (THP) on Linux systems. On Android only this is\n   by default off. When set to `0`, THP is disabled for the process that mimalloc runs in. \n   In mimalloc v3.2+, If enabled, mimalloc also sets\n   the `MIMALLOC_MINIMAL_PURGE_SIZE` to 2MiB to avoid potentially breaking up transparent huge pages.\n- `MIMALLOC_USE_NUMA_NODES=N`: pretend there are at most `N` NUMA nodes. If not set, the actual NUMA nodes are detected\n   at runtime. Setting `N` to 1 may avoid problems in some virtual environments. Also, setting it to a lower number than\n   the actual NUMA nodes is fine and will only cause threads to potentially allocate more memory across actual NUMA\n   nodes (but this can happen in any case as NUMA local allocation is always a best effort but not guaranteed).\n- `MIMALLOC_ALLOW_LARGE_OS_PAGES=0`: Set to 1 to use large OS pages (2 or 4MiB) when available; for some workloads this can\n   significantly improve performance. However, large OS pages cannot be purged or shared with other processes so may lead\n   to increased memory usage in some cases.\n   Use `MIMALLOC_VERBOSE` to check if the large OS pages are enabled -- usually one needs\n   to explicitly give permissions for large OS pages (as on [Windows][windows-huge] and [Linux][linux-huge]). However, sometimes\n   the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that\n   can have fragmented memory (for that reason, we generally recommend to use `MIMALLOC_RESERVE_HUGE_OS_PAGES` instead whenever possible).\n- `MIMALLOC_RESERVE_HUGE_OS_PAGES=N`: where `N` is the number of 1GiB _huge_ OS pages. This reserves the huge pages at\n   startup and sometimes this can give a large (latency) performance improvement on big workloads.\n   Usually it is better to not use `MIMALLOC_ALLOW_LARGE_OS_PAGES=1` in combination with this setting. Just like large\n   OS pages, use with care as reserving\n   contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at\n   startup only once).\n   Note that we usually need to explicitly give permission for huge OS pages (as on [Windows][windows-huge] and [Linux][linux-huge])).\n   The huge pages are usually allocated evenly among NUMA nodes.\n   We can use `MIMALLOC_RESERVE_HUGE_OS_PAGES_AT=N` where `N` is the numa node (starting at 0) to allocate all\n   the huge pages at a specific numa node instead.\n\nUse caution when using `fork` in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write\nfor all pages in the original process including the huge OS pages. When any memory is now written in that area, the\nOS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in large increments.\n\n[linux-huge]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/tuning_and_optimizing_red_hat_enterprise_linux_for_oracle_9i_and_10g_databases/sect-oracle_9i_and_10g_tuning_guide-large_memory_optimization_big_pages_and_huge_pages-configuring_huge_pages_in_red_hat_enterprise_linux_4_or_5\n[windows-huge]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows?view=sql-server-2017\n\n*/\n\n/*! \\page overrides Overriding Malloc\n\nOverriding the standard `malloc` (and `new`) can be done either _dynamically_ or _statically_.\n\n## Dynamic override\n\nThis is the recommended way to override the standard malloc interface.\n\n### Dynamic Override on Linux, BSD\n\nOn these ELF-based systems we preload the mimalloc shared\nlibrary so all calls to the standard `malloc` interface are\nresolved to the _mimalloc_ library.\n```\n> env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram\n```\n\nYou can set extra environment variables to check that mimalloc is running,\nlike:\n```\n> env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram\n```\nor run with the debug version to get detailed statistics:\n```\n> env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram\n```\n\n### Dynamic Override on MacOS\n\nOn macOS we can also preload the mimalloc shared\nlibrary so all calls to the standard `malloc` interface are\nresolved to the _mimalloc_ library.\n```\n> env DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram\n```\n\nNote that certain security restrictions may apply when doing this from\nthe [shell](https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash).\n\n\n### Dynamic Override on Windows\n\n<span id=\"override_on_windows\">Dynamically overriding on mimalloc on Windows</span> \nis robust and has the particular advantage to be able to redirect all malloc/free calls \nthat go through the (dynamic) C runtime allocator, including those from other DLL's or \nlibraries. As it intercepts all allocation calls on a low level, it can be used reliably \non large programs that include other 3rd party components.\nThere are four requirements to make the overriding work well:\n\n1. Use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch).\n\n2. Link your program explicitly with the `mimalloc.lib` export library for the `mimalloc.dll`.\n   (which must be compiled with `-DMI_OVERRIDE=ON`, which is the default though).\n   To ensure the `mimalloc.dll` is actually loaded at run-time it is easiest \n   to insert some call to the mimalloc API in the `main` function, like `mi_version()`\n   (or use the `/include:mi_version` switch on the linker command, or\n   similarly, `#pragma comment(linker, \"/include:mi_version\")` in some source file). \n   See the `mimalloc-test-override` project for an example on how to use this. \n\n3. The `mimalloc-redirect.dll` must be put in the same directory as the main \n   `mimalloc.dll` at runtime (as it is a dependency of that DLL).\n   The redirection DLL ensures that all calls to the C runtime malloc API get \n   redirected to mimalloc functions (which reside in `mimalloc.dll`).\n\n4. Ensure the `mimalloc.dll` comes as early as possible in the import\n   list of the final executable (so it can intercept all potential allocations).\n   You can use `minject -l <exe>` to check this if needed.\n\nFor best performance on Windows with C++, it\nis also recommended to also override the `new`/`delete` operations (by including\n[`mimalloc-new-delete.h`](https://github.com/microsoft/mimalloc/blob/main/include/mimalloc-new-delet.h)\na single(!) source file in your project).\n\nThe environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic\noverriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully \nredirected.\n\nFor different platforms than x64, you may need a specific [redirection dll](bin).\nFurthermore, we cannot always re-link an executable or ensure `mimalloc.dll` comes\nfirst in the import table. In such cases the [`minject`](bin) tool can be used\nto patch the executable's import tables.\n\n\n## Static override\n\nOn Unix-like systems, you can also statically link with _mimalloc_ to override the standard\nmalloc interface. The recommended way is to link the final program with the\n_mimalloc_ single object file (`mimalloc.o`). We use\nan object file instead of a library file as linkers give preference to\nthat over archives to resolve symbols. To ensure that the standard\nmalloc interface resolves to the _mimalloc_ library, link it as the first\nobject file. For example:\n```\n> gcc -o myprogram mimalloc.o  myfile1.c ...\n```\n\nAnother way to override statically that works on all platforms, is to\nlink statically to mimalloc (as shown in the introduction) and include a\nheader file in each source file that re-defines `malloc` etc. to `mi_malloc`.\nThis is provided by [`mimalloc-override.h`](https://github.com/microsoft/mimalloc/blob/main/include/mimalloc-override.h). This only works reliably though if all sources are\nunder your control or otherwise mixing of pointers from different heaps may occur!\n\n## List of Overrides:\n\nThe specific functions that get redirected to the _mimalloc_ library are:\n\n```\n// C\nvoid*  malloc(size_t size);\nvoid*  calloc(size_t size, size_t n);\nvoid*  realloc(void* p, size_t newsize);\nvoid   free(void* p);\n\nvoid*  aligned_alloc(size_t alignment, size_t size);\nchar*  strdup(const char* s);\nchar*  strndup(const char* s, size_t n);\nchar*  realpath(const char* fname, char* resolved_name);\n\n\n// C++\nvoid   operator delete(void* p);\nvoid   operator delete[](void* p);\n\nvoid*  operator new(std::size_t n) noexcept(false);\nvoid*  operator new[](std::size_t n) noexcept(false);\nvoid*  operator new( std::size_t n, std::align_val_t align) noexcept(false);\nvoid*  operator new[]( std::size_t n, std::align_val_t align) noexcept(false);\n\nvoid*  operator new  ( std::size_t count, const std::nothrow_t& tag);\nvoid*  operator new[]( std::size_t count, const std::nothrow_t& tag);\nvoid*  operator new  ( std::size_t count, std::align_val_t al, const std::nothrow_t&);\nvoid*  operator new[]( std::size_t count, std::align_val_t al, const std::nothrow_t&);\n\n// Posix\nint    posix_memalign(void** p, size_t alignment, size_t size);\n\n// Linux\nvoid*  memalign(size_t alignment, size_t size);\nvoid*  valloc(size_t size);\nvoid*  pvalloc(size_t size);\nsize_t malloc_usable_size(void *p);\nvoid*  reallocf(void* p, size_t newsize);\n\n// macOS\nvoid   vfree(void* p);\nsize_t malloc_size(const void* p);\nsize_t malloc_good_size(size_t size);\n\n// BSD\nvoid*  reallocarray( void* p, size_t count, size_t size );\nvoid*  reallocf(void* p, size_t newsize);\nvoid   cfree(void* p);\n\n// NetBSD\nint    reallocarr(void* p, size_t count, size_t size);\n\n// Windows\nvoid*  _expand(void* p, size_t newsize);\nsize_t _msize(void* p);\n\nvoid*  _malloc_dbg(size_t size, int block_type, const char* fname, int line);\nvoid*  _realloc_dbg(void* p, size_t newsize, int block_type, const char* fname, int line);\nvoid*  _calloc_dbg(size_t count, size_t size, int block_type, const char* fname, int line);\nvoid*  _expand_dbg(void* p, size_t size, int block_type, const char* fname, int line);\nsize_t _msize_dbg(void* p, int block_type);\nvoid   _free_dbg(void* p, int block_type);\n```\n\n*/\n\n/*! \\page modes Build Modes\n\nWe can build mimalloc in various modes.\n\n## Secure Mode\n\n_mimalloc_ can be build in secure mode by using the `-DMI_SECURE=ON` flags in `cmake`. This build enables various mitigations\nto make mimalloc more robust against exploits. In particular:\n\n- All internal mimalloc pages are surrounded by guard pages and the heap metadata is behind a guard page as well (so a buffer overflow\n  exploit cannot reach into the metadata).\n- All free list pointers are\n  [encoded](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396)\n  with per-page keys which is used both to prevent overwrites with a known pointer, as well as to detect heap corruption.\n- Double free's are detected (and ignored).\n- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to\n  mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc\n  from the OS are also address randomized.\n\nAs always, evaluate with care as part of an overall security strategy as all of the above are mitigations but not guarantees.\n\n## Debug Mode\n\nWhen _mimalloc_ is built using debug mode, (`-DCMAKE_BUILD_TYPE=Debug`), \nvarious checks are done at runtime to catch development errors.\n\n- Statistics are maintained in detail for each object size. They can be shown using `MIMALLOC_SHOW_STATS=1` at runtime.\n- All objects have padding at the end to detect (byte precise) heap block overflows.\n- Double free's, and freeing invalid heap pointers are detected.\n- Corrupted free-lists and some forms of use-after-free are detected.\n\n## Guarded Mode\n\n<span id=\"guarded\">_mimalloc_ can be build in guarded mode using the `-DMI_GUARDED=ON` flags in `cmake`.</span>\nThis enables placing OS guard pages behind certain object allocations to catch buffer overflows as they occur.\nThis can be invaluable to catch buffer-overflow bugs in large programs. However, it also means that any object\nallocated with a guard page takes at least 8 KiB memory for the guard page and its alignment. As such, allocating\na guard page for every allocation may be too expensive both in terms of memory, and in terms of performance with\nmany system calls. Therefore, there are various environment variables (and options) to tune this:\n\n- `MIMALLOC_GUARDED_SAMPLE_RATE=N`: Set the sample rate to `N` (by default 4000). This mode places a guard page\n  behind every `N` suitable object allocations (per thread). Since the performance in guarded mode without placing\n  guard pages is close to release mode, this can be used to enable guard pages even in production to catch latent \n  buffer overflow bugs. Set the sample rate to `1` to guard every object, and to `0` to place no guard pages at all.\n\n- `MIMALLOC_GUARDED_SAMPLE_SEED=N`: Start sampling at `N` (by default random). Can be used to reproduce a buffer\n  overflow if needed.\n\n- `MIMALLOC_GUARDED_MIN=N`, `MIMALLOC_GUARDED_MAX=N`: Minimal and maximal _rounded_ object sizes for which a guard \n  page is considered (`0` and `1GiB` respectively). If you suspect a buffer overflow occurs with an object of size\n  141, set the minimum and maximum to `148` and the sample rate to `1` to have all of those guarded.\n\n- `MIMALLOC_GUARDED_PRECISE=1`: If we have an object of size 13, we would usually place it an aligned 16 bytes in\n  front of the guard page. Using `MIMALLOC_GUARDED_PRECISE` places it exactly 13 bytes before a page so that even\n  a 1 byte overflow is detected. This violates the C/C++ minimal alignment guarantees though so use with care.\n\n*/\n\n/*! \\page tools Tools\n\nGenerally, we recommend using the standard allocator with memory tracking tools, but mimalloc\ncan also be build to support the [address sanitizer][asan] or the excellent [Valgrind] tool.\nMoreover, it can be build to support Windows event tracing ([ETW]).\nThis has a small performance overhead but does allow detecting memory leaks and byte-precise\nbuffer overflows directly on final executables. See also the `test/test-wrong.c` file to test with various tools.\n\n## Valgrind\n\nTo build with [valgrind] support, use the `MI_TRACK_VALGRIND=ON` cmake option:\n\n```\n> cmake ../.. -DMI_TRACK_VALGRIND=ON\n```\n\nThis can also be combined with secure mode or debug mode.\nYou can then run your programs directly under valgrind:\n\n```\n> valgrind <myprogram>\n```\n\nIf you rely on overriding `malloc`/`free` by mimalloc (instead of using the `mi_malloc`/`mi_free` API directly),\nyou also need to tell `valgrind` to not intercept those calls itself, and use:\n\n```\n> MIMALLOC_SHOW_STATS=1 valgrind  --soname-synonyms=somalloc=*mimalloc* -- <myprogram>\n```\n\nBy setting the `MIMALLOC_SHOW_STATS` environment variable you can check that mimalloc is indeed\nused and not the standard allocator. Even though the [Valgrind option][valgrind-soname]\nis called `--soname-synonyms`, this also works when overriding with a static library or object file.\nTo dynamically override mimalloc using `LD_PRELOAD` together with `valgrind`, use:\n\n```\n> valgrind --trace-children=yes --soname-synonyms=somalloc=*mimalloc* /usr/bin/env LD_PRELOAD=/usr/lib/libmimalloc.so -- <myprogram>\n```\n\nSee also the `test/test-wrong.c` file to test with `valgrind`.\n\nValgrind support is in its initial development -- please report any issues.\n\n[Valgrind]: https://valgrind.org/\n[valgrind-soname]: https://valgrind.org/docs/manual/manual-core.html#opt.soname-synonyms\n\n## ASAN\n\nTo build with the address sanitizer, use the `-DMI_TRACK_ASAN=ON` cmake option:\n\n```\n> cmake ../.. -DMI_TRACK_ASAN=ON\n```\n\nThis can also be combined with secure mode or debug mode.\nYou can then run your programs as:'\n\n```\n> ASAN_OPTIONS=verbosity=1 <myprogram>\n```\n\nWhen you link a program with an address sanitizer build of mimalloc, you should\ngenerally compile that program too with the address sanitizer enabled.\nFor example, assuming you build mimalloc in `out/debug`:\n\n```\nclang -g -o test-wrong -Iinclude test/test-wrong.c out/debug/libmimalloc-asan-debug.a -lpthread -fsanitize=address -fsanitize-recover=address\n```\n\nSince the address sanitizer redirects the standard allocation functions, on some platforms (macOSX for example)\nit is required to compile mimalloc with `-DMI_OVERRIDE=OFF`.\nAddress sanitizer support is in its initial development -- please report any issues.\n\n[asan]: https://github.com/google/sanitizers/wiki/AddressSanitizer\n\n## ETW\n\nEvent tracing for Windows ([ETW]) provides a high performance way to capture all allocations though\nmimalloc and analyze them later. To build with ETW support, use the `-DMI_TRACK_ETW=ON` cmake option.\n\nYou can then capture an allocation trace using the Windows performance recorder (WPR), using the\n`src/prim/windows/etw-mimalloc.wprp` profile. In an admin prompt, you can use:\n```\n> wpr -start src\\prim\\windows\\etw-mimalloc.wprp -filemode\n> <my_mimalloc_program>\n> wpr -stop <my_mimalloc_program>.etl\n```\nand then open `<my_mimalloc_program>.etl` in the Windows Performance Analyzer (WPA), or\nuse a tool like [TraceControl] that is specialized for analyzing mimalloc traces.\n\n[ETW]: https://learn.microsoft.com/en-us/windows-hardware/test/wpt/event-tracing-for-windows\n[TraceControl]: https://github.com/xinglonghe/TraceControl\n\n*/\n\n/*! \\page bench Performance\n\nWe tested _mimalloc_ against many other top allocators over a wide\nrange of benchmarks, ranging from various real world programs to\nsynthetic benchmarks that see how the allocator behaves under more\nextreme circumstances.\n\nIn our benchmarks, _mimalloc_ always outperforms all other leading\nallocators (_jemalloc_, _tcmalloc_, _Hoard_, etc) (Jan 2021),\nand usually uses less memory (up to 25% more in the worst case).\nA nice property is that it does *consistently* well over the wide\nrange of benchmarks.\n\nSee the [Performance](https://github.com/microsoft/mimalloc#Performance)\nsection in the _mimalloc_ repository for benchmark results,\nor the the technical report for detailed benchmark results.\n\n*/\n"
  },
  {
    "path": "doc/mimalloc-doxygen.css",
    "content": "#projectlogo img {\n\tpadding: 1ex;\n}\ntt, code, kbd, samp, div.memproto, div.fragment, div.line, table.memname {\n\tfont-family: Consolas, Monaco, Inconsolata, \"Courier New\", monospace;\n}\n.image img, .textblock img {\n\tmax-width: 99%;\n\tmax-height: 350px;\n}\ntable.memname, .memname{\n\tfont-weight: bold;\n}\ncode {\n\tbackground-color: #EEE;\n\tpadding: 0ex 0.25ex;\n}\nbody {\n\tmargin: 1ex 1ex 0ex 1ex;\n\tborder: 1px solid black;\n}\n.contents table, .contents div, .contents p, .contents dl {\n\tfont-size: 16px;\n\tline-height: 1.44;\n}\nbody #nav-tree .label {\n\tfont-size: 14px;\n}\na{\n\ttext-decoration: underline;\n}\n#side-nav {\n\tmargin-left: 1ex;\n\tborder-left: 1px solid black;\n}\n#nav-tree {\n\tpadding-left: 1ex;\n}\n#nav-path {\n\tdisplay: none;\n}\ndiv.fragment {\n\tbackground-color: #EEE;\n\tpadding: 0.25ex 0.5ex;\n\tborder-color: black;\n}\n#nav-sync img {\n\tdisplay: none;\n}\nh1,h2,h3,h4,h5,h6 {\n\ttransition:none;\n}\n.memtitle {\n\tbackground-image: none;\n\tbackground-color: #EEE;\n}\ntable.memproto, .memproto {\n\ttext-shadow: none;\n\tfont-size: 110%;\n}\n"
  },
  {
    "path": "ide/vs2022/mimalloc-lib.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{ABB5EAE7-B3E6-432E-B636-333449892EA6}</ProjectGuid>\n    <RootNamespace>mimalloc-lib</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-lib</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>StaticLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.lib</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=3;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=3;MI_GUARDED=0;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Link>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=3;MI_GUARDED=0;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Link>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=3;MI_GUARDED=0;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Link>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n    </Link>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n      <ExceptionHandling>Sync</ExceptionHandling>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level4</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <ConformanceMode>Default</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <LanguageStandard>stdcpp20</LanguageStandard>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n      <ExceptionHandling>Sync</ExceptionHandling>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n    <Lib>\n      <AdditionalLibraryDirectories>\n      </AdditionalLibraryDirectories>\n      <AdditionalDependencies>\n      </AdditionalDependencies>\n    </Lib>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\alloc-aligned.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">false</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-override.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-posix.c\" />\n    <ClCompile Include=\"..\\..\\src\\alloc.c\" />\n    <ClCompile Include=\"..\\..\\src\\arena-abandon.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena.c\" />\n    <ClCompile Include=\"..\\..\\src\\bitmap.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">false</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\free.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\heap.c\" />\n    <ClCompile Include=\"..\\..\\src\\init.c\" />\n    <ClCompile Include=\"..\\..\\src\\libc.c\" />\n    <ClCompile Include=\"..\\..\\src\\prim\\prim.c\" />\n    <ClCompile Include=\"..\\..\\src\\prim\\windows\\prim.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\options.c\" />\n    <ClCompile Include=\"..\\..\\src\\page-queue.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page.c\" />\n    <ClCompile Include=\"..\\..\\src\\random.c\" />\n    <ClCompile Include=\"..\\..\\src\\segment-map.c\" />\n    <ClCompile Include=\"..\\..\\src\\segment.c\" />\n    <ClCompile Include=\"..\\..\\src\\os.c\" />\n    <ClCompile Include=\"..\\..\\src\\stats.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc.h\" />\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc-override.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-new-delete.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-stats.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\atomic.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\internal.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\prim.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\track.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\types.h\" />\n    <ClInclude Include=\"..\\..\\src\\bitmap.h\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-lib.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\alloc.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-aligned.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-override.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-posix.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\bitmap.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\free.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\heap.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\init.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\libc.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\options.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\os.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page-queue.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\prim\\windows\\prim.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\prim\\prim.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\random.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\segment.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\segment-map.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\stats.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena-abandon.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\atomic.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\bitmap.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\internal.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-new-delete.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc-override.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\track.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\types.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\prim.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-stats.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"Headers\">\n      <UniqueIdentifier>{1430490c-e711-4ace-a1b8-36f4d5105873}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Sources\">\n      <UniqueIdentifier>{461c78ef-04b0-44d1-a0ca-7d488abaa592}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-override-dll.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{ABB5EAE7-B3E6-432E-B636-333449892EA7}</ProjectGuid>\n    <RootNamespace>mimalloc-override-dll</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-override-dll</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(SolutionDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n    <TargetExt>.dll</TargetExt>\n    <TargetName>mimalloc</TargetName>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <CompileAs>CompileAsCpp</CompileAs>\n    </ClCompile>\n    <Link>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <IgnoreSpecificDefaultLibraries>\n      </IgnoreSpecificDefaultLibraries>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect32.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>Copy mimalloc-redirect32.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=4;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <CompileAs>CompileAsCpp</CompileAs>\n    </ClCompile>\n    <Link>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <IgnoreSpecificDefaultLibraries>\n      </IgnoreSpecificDefaultLibraries>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=4;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <CompileAs>CompileAsCpp</CompileAs>\n    </ClCompile>\n    <Link>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect-arm64.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <IgnoreSpecificDefaultLibraries>\n      </IgnoreSpecificDefaultLibraries>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect-arm64.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect-arm64.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_DEBUG=4;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <SupportJustMyCode>false</SupportJustMyCode>\n      <CompileAs>CompileAsCpp</CompileAs>\n    </ClCompile>\n    <Link>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect-arm64ec.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <IgnoreSpecificDefaultLibraries>\n      </IgnoreSpecificDefaultLibraries>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect-arm64ec.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect-arm64ec.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect32.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>Copy mimalloc-redirect32.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect-arm64.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect-arm64.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect-arm64.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>../../include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>\n      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\n      <WholeProgramOptimization>false</WholeProgramOptimization>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <CompileAs>CompileAsCpp</CompileAs>\n      <BufferSecurityCheck>false</BufferSecurityCheck>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <AdditionalDependencies>$(ProjectDir)\\..\\..\\bin\\mimalloc-redirect-arm64ec.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <ModuleDefinitionFile>\n      </ModuleDefinitionFile>\n      <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>\n      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>\n      <ImportLibrary>$(OutDir)$(TargetName).dll.lib</ImportLibrary>\n      <ProgramDatabaseFile>$(OutDir)$(TargetName).dll.pdb</ProgramDatabaseFile>\n    </Link>\n    <PostBuildEvent>\n      <Command>COPY /Y \"$(ProjectDir)..\\..\\bin\\mimalloc-redirect-arm64ec.dll\" \"$(OutputPath)\"</Command>\n    </PostBuildEvent>\n    <PostBuildEvent>\n      <Message>copy mimalloc-redirect-arm64ec.dll to the output directory</Message>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-etw-gen.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-etw.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-new-delete.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-override.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc-stats.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\atomic.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\internal.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\prim.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\track.h\" />\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\types.h\" />\n    <ClInclude Include=\"..\\..\\src\\bitmap.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\alloc-aligned.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">false</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-override.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-posix.c\" />\n    <ClCompile Include=\"..\\..\\src\\alloc.c\" />\n    <ClCompile Include=\"..\\..\\src\\arena-abandon.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena.c\" />\n    <ClCompile Include=\"..\\..\\src\\bitmap.c\" />\n    <ClCompile Include=\"..\\..\\src\\heap.c\" />\n    <ClCompile Include=\"..\\..\\src\\init.c\" />\n    <ClCompile Include=\"..\\..\\src\\libc.c\" />\n    <ClCompile Include=\"..\\..\\src\\prim\\prim.c\" />\n    <ClCompile Include=\"..\\..\\src\\prim\\windows\\prim.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\options.c\" />\n    <ClCompile Include=\"..\\..\\src\\os.c\" />\n    <ClCompile Include=\"..\\..\\src\\page-queue.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page.c\" />\n    <ClCompile Include=\"..\\..\\src\\random.c\" />\n    <ClCompile Include=\"..\\..\\src\\segment-map.c\" />\n    <ClCompile Include=\"..\\..\\src\\segment.c\" />\n    <ClCompile Include=\"..\\..\\src\\stats.c\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-override-dll.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\alloc.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-aligned.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-override.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\alloc-posix.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\bitmap.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\heap.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\init.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\libc.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\options.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\os.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\page-queue.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\prim\\windows\\prim.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\prim\\prim.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\random.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\segment.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\segment-map.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\stats.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\arena-abandon.c\">\n      <Filter>Sources</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\atomic.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\bitmap.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\internal.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"$(ProjectDir)..\\..\\include\\mimalloc.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-etw.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-etw-gen.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-new-delete.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-override.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\track.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\types.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc\\prim.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\include\\mimalloc-stats.h\">\n      <Filter>Headers</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"Headers\">\n      <UniqueIdentifier>{262c6c21-e270-4ba6-bd63-4ac999307e4e}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Sources\">\n      <UniqueIdentifier>{94b40bdc-a741-45dd-81aa-c05fabcd2970}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-override-test-dep.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{FEF7869F-750E-4C21-A04D-22707CC66879}</ProjectGuid>\n    <RootNamespace>mimalloc-test-override-dep</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-test-override-dep</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\test\\main-override-dep.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\test\\main-override-dep.h\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-override-test.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{FEF7868F-750E-4C21-A04D-22707CC66879}</ProjectGuid>\n    <RootNamespace>mimalloc-override-test</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-test-override</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\n      <ExceptionHandling>Sync</ExceptionHandling>\n      <CompileAs>Default</CompileAs>\n      <SupportJustMyCode>false</SupportJustMyCode>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent />\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <LanguageStandard>Default</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <LanguageStandard>Default</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <LanguageStandard>Default</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>\n      <LanguageStandard>Default</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n      <EntryPointSymbol>\n      </EntryPointSymbol>\n      <AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>\n    </Link>\n    <PostBuildEvent>\n      <Command>\n      </Command>\n    </PostBuildEvent>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\test\\main-override.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"mimalloc-override-dll.vcxproj\">\n      <Project>{abb5eae7-b3e6-432e-b636-333449892ea7}</Project>\n    </ProjectReference>\n    <ProjectReference Include=\"mimalloc-override-test-dep.vcxproj\">\n      <Project>{fef7869f-750e-4c21-a04d-22707cc66879}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-test-api.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{FFF7958F-750E-4C21-A04D-22707CC66878}</ProjectGuid>\n    <RootNamespace>mimalloc-test-api</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-test-api</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\test\\test-api-fill.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">true</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">true</ExcludedFromBuild>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\test\\test-api.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">false</ExcludedFromBuild>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"mimalloc-lib.vcxproj\">\n      <Project>{abb5eae7-b3e6-432e-b636-333449892ea6}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-test-stress.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{FEF7958F-750E-4C21-A04D-22707CC66878}</ProjectGuid>\n    <RootNamespace>mimalloc-test-stress</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-test-stress</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <EnableEnhancedInstructionSet>CPUExtensionRequirementsARMv81</EnableEnhancedInstructionSet>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\test\\test-stress.c\">\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">false</ExcludedFromBuild>\n      <ExcludedFromBuild Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">false</ExcludedFromBuild>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"mimalloc-override-dll.vcxproj\">\n      <Project>{abb5eae7-b3e6-432e-b636-333449892ea7}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc-test.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|ARM64\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|ARM64EC\">\n      <Configuration>Debug</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|ARM64EC\">\n      <Configuration>Release</Configuration>\n      <Platform>ARM64EC</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{FEF7858F-750E-4C21-A04D-22707CC66878}</ProjectGuid>\n    <RootNamespace>mimalloctest</RootNamespace>\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\n    <ProjectName>mimalloc-test-static</ProjectName>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v143</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <OutDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(Configuration)\\</OutDir>\n    <IntDir>$(ProjectDir)..\\..\\out\\msvc-$(Platform)\\$(ProjectName)\\$(Configuration)\\</IntDir>\n  </PropertyGroup>\n  <PropertyGroup Label=\"Vcpkg\">\n    <VcpkgEnabled>false</VcpkgEnabled>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64EC'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>MaxSpeed</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>true</ConformanceMode>\n      <AdditionalIncludeDirectories>..\\..\\include</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>_MBCS;%(PreprocessorDefinitions);NDEBUG</PreprocessorDefinitions>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Console</SubSystem>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\test\\main-override-static.c\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"mimalloc-lib.vcxproj\">\n      <Project>{abb5eae7-b3e6-432e-b636-333449892ea6}</Project>\n    </ProjectReference>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "ide/vs2022/mimalloc.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.12.35527.113\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-lib\", \"mimalloc-lib.vcxproj\", \"{ABB5EAE7-B3E6-432E-B636-333449892EA6}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-test-static\", \"mimalloc-test.vcxproj\", \"{FEF7858F-750E-4C21-A04D-22707CC66878}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-override-dll\", \"mimalloc-override-dll.vcxproj\", \"{ABB5EAE7-B3E6-432E-B636-333449892EA7}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-test-override-dep\", \"mimalloc-override-test-dep.vcxproj\", \"{FEF7869F-750E-4C21-A04D-22707CC66879}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-test-override\", \"mimalloc-override-test.vcxproj\", \"{FEF7868F-750E-4C21-A04D-22707CC66879}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-test-stress\", \"mimalloc-test-stress.vcxproj\", \"{FEF7958F-750E-4C21-A04D-22707CC66878}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"mimalloc-test-api\", \"mimalloc-test-api.vcxproj\", \"{FFF7958F-750E-4C21-A04D-22707CC66878}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|ARM64 = Debug|ARM64\r\n\t\tDebug|ARM64EC = Debug|ARM64EC\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|ARM64 = Release|ARM64\r\n\t\tRelease|ARM64EC = Release|ARM64EC\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.Build.0 = Release|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.Build.0 = Release|Win32\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.Build.0 = Release|x64\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.Build.0 = Release|Win32\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|x64.Build.0 = Release|x64\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{FEF7869F-750E-4C21-A04D-22707CC66879}.Release|x86.Build.0 = Release|Win32\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.Build.0 = Release|x64\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.Build.0 = Release|Win32\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|ARM64EC.Build.0 = Debug|ARM64EC\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.ActiveCfg = Release|ARM64EC\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|ARM64EC.Build.0 = Release|ARM64EC\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {4297F93D-486A-4243-995F-7D32F59AE82A}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "include/mimalloc/atomic.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_ATOMIC_H\n#define MIMALLOC_ATOMIC_H\n\n// include windows.h or pthreads.h\n#if defined(_WIN32)\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n#elif !defined(__wasi__) && (!defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__))\n#define  MI_USE_PTHREADS\n#include <pthread.h>\n#endif\n\n// --------------------------------------------------------------------------------------------\n// Atomics\n// We need to be portable between C, C++, and MSVC.\n// We base the primitives on the C/C++ atomics and create a minimal wrapper for MSVC in C compilation mode.\n// This is why we try to use only `uintptr_t` and `<type>*` as atomic types.\n// To gain better insight in the range of used atomics, we use explicitly named memory order operations\n// instead of passing the memory order as a parameter.\n// -----------------------------------------------------------------------------------------------\n\n#if defined(__cplusplus)\n// Use C++ atomics\n#include <atomic>\n#define  _Atomic(tp)              std::atomic<tp>\n#define  mi_atomic(name)          std::atomic_##name\n#define  mi_memory_order(name)    std::memory_order_##name\n#if (__cplusplus >= 202002L)      // c++20, see issue #571\n #define MI_ATOMIC_VAR_INIT(x)    x\n#elif !defined(ATOMIC_VAR_INIT)\n #define MI_ATOMIC_VAR_INIT(x)    x\n#else\n #define MI_ATOMIC_VAR_INIT(x)    ATOMIC_VAR_INIT(x)\n#endif\n#elif defined(_MSC_VER)\n// Use MSVC C wrapper for C11 atomics\n#define  _Atomic(tp)              tp\n#define  MI_ATOMIC_VAR_INIT(x)    x\n#define  mi_atomic(name)          mi_atomic_##name\n#define  mi_memory_order(name)    mi_memory_order_##name\n#else\n// Use C11 atomics\n#include <stdatomic.h>\n#define  mi_atomic(name)          atomic_##name\n#define  mi_memory_order(name)    memory_order_##name\n#if (__STDC_VERSION__ >= 201710L) // c17, see issue #735\n #define MI_ATOMIC_VAR_INIT(x)    x\n#elif !defined(ATOMIC_VAR_INIT)\n #define MI_ATOMIC_VAR_INIT(x)    x\n#else\n #define MI_ATOMIC_VAR_INIT(x)    ATOMIC_VAR_INIT(x)\n#endif\n#endif\n\n// Various defines for all used memory orders in mimalloc\n#define mi_atomic_cas_weak(p,expected,desired,mem_success,mem_fail)  \\\n  mi_atomic(compare_exchange_weak_explicit)(p,expected,desired,mem_success,mem_fail)\n\n#define mi_atomic_cas_strong(p,expected,desired,mem_success,mem_fail)  \\\n  mi_atomic(compare_exchange_strong_explicit)(p,expected,desired,mem_success,mem_fail)\n\n#define mi_atomic_load_acquire(p)                mi_atomic(load_explicit)(p,mi_memory_order(acquire))\n#define mi_atomic_load_relaxed(p)                mi_atomic(load_explicit)(p,mi_memory_order(relaxed))\n#define mi_atomic_store_release(p,x)             mi_atomic(store_explicit)(p,x,mi_memory_order(release))\n#define mi_atomic_store_relaxed(p,x)             mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed))\n#define mi_atomic_exchange_relaxed(p,x)          mi_atomic(exchange_explicit)(p,x,mi_memory_order(relaxed))\n#define mi_atomic_exchange_release(p,x)          mi_atomic(exchange_explicit)(p,x,mi_memory_order(release))\n#define mi_atomic_exchange_acq_rel(p,x)          mi_atomic(exchange_explicit)(p,x,mi_memory_order(acq_rel))\n#define mi_atomic_cas_weak_release(p,exp,des)    mi_atomic_cas_weak(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed))\n#define mi_atomic_cas_weak_acq_rel(p,exp,des)    mi_atomic_cas_weak(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire))\n#define mi_atomic_cas_strong_release(p,exp,des)  mi_atomic_cas_strong(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed))\n#define mi_atomic_cas_strong_acq_rel(p,exp,des)  mi_atomic_cas_strong(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire))\n\n#define mi_atomic_add_relaxed(p,x)               mi_atomic(fetch_add_explicit)(p,x,mi_memory_order(relaxed))\n#define mi_atomic_sub_relaxed(p,x)               mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(relaxed))\n#define mi_atomic_add_acq_rel(p,x)               mi_atomic(fetch_add_explicit)(p,x,mi_memory_order(acq_rel))\n#define mi_atomic_sub_acq_rel(p,x)               mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(acq_rel))\n#define mi_atomic_and_acq_rel(p,x)               mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(acq_rel))\n#define mi_atomic_or_acq_rel(p,x)                mi_atomic(fetch_or_explicit)(p,x,mi_memory_order(acq_rel))\n\n#define mi_atomic_increment_relaxed(p)           mi_atomic_add_relaxed(p,(uintptr_t)1)\n#define mi_atomic_decrement_relaxed(p)           mi_atomic_sub_relaxed(p,(uintptr_t)1)\n#define mi_atomic_increment_acq_rel(p)           mi_atomic_add_acq_rel(p,(uintptr_t)1)\n#define mi_atomic_decrement_acq_rel(p)           mi_atomic_sub_acq_rel(p,(uintptr_t)1)\n\nstatic inline void mi_atomic_yield(void);\nstatic inline intptr_t mi_atomic_addi(_Atomic(intptr_t)*p, intptr_t add);\nstatic inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub);\n\n\n#if defined(__cplusplus) || !defined(_MSC_VER)\n\n// In C++/C11 atomics we have polymorphic atomics so can use the typed `ptr` variants (where `tp` is the type of atomic value)\n// We use these macros so we can provide a typed wrapper in MSVC in C compilation mode as well\n#define mi_atomic_load_ptr_acquire(tp,p)                mi_atomic_load_acquire(p)\n#define mi_atomic_load_ptr_relaxed(tp,p)                mi_atomic_load_relaxed(p)\n\n// In C++ we need to add casts to help resolve templates if NULL is passed\n#if defined(__cplusplus)\n#define mi_atomic_store_ptr_release(tp,p,x)             mi_atomic_store_release(p,(tp*)x)\n#define mi_atomic_store_ptr_relaxed(tp,p,x)             mi_atomic_store_relaxed(p,(tp*)x)\n#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des)    mi_atomic_cas_weak_release(p,exp,(tp*)des)\n#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des)    mi_atomic_cas_weak_acq_rel(p,exp,(tp*)des)\n#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des)  mi_atomic_cas_strong_release(p,exp,(tp*)des)\n#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des)  mi_atomic_cas_strong_acq_rel(p,exp,(tp*)des)\n#define mi_atomic_exchange_ptr_relaxed(tp,p,x)          mi_atomic_exchange_relaxed(p,(tp*)x)\n#define mi_atomic_exchange_ptr_release(tp,p,x)          mi_atomic_exchange_release(p,(tp*)x)\n#define mi_atomic_exchange_ptr_acq_rel(tp,p,x)          mi_atomic_exchange_acq_rel(p,(tp*)x)\n#else\n#define mi_atomic_store_ptr_release(tp,p,x)             mi_atomic_store_release(p,x)\n#define mi_atomic_store_ptr_relaxed(tp,p,x)             mi_atomic_store_relaxed(p,x)\n#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des)    mi_atomic_cas_weak_release(p,exp,des)\n#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des)    mi_atomic_cas_weak_acq_rel(p,exp,des)\n#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des)  mi_atomic_cas_strong_release(p,exp,des)\n#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des)  mi_atomic_cas_strong_acq_rel(p,exp,des)\n#define mi_atomic_exchange_ptr_relaxed(tp,p,x)          mi_atomic_exchange_relaxed(p,x)\n#define mi_atomic_exchange_ptr_release(tp,p,x)          mi_atomic_exchange_release(p,x)\n#define mi_atomic_exchange_ptr_acq_rel(tp,p,x)          mi_atomic_exchange_acq_rel(p,x)\n#endif\n\n// These are used by the statistics\nstatic inline int64_t mi_atomic_addi64_relaxed(volatile int64_t* p, int64_t add) {\n  return mi_atomic(fetch_add_explicit)((_Atomic(int64_t)*)p, add, mi_memory_order(relaxed));\n}\nstatic inline void mi_atomic_void_addi64_relaxed(volatile int64_t* p, const volatile int64_t* padd) {\n  const int64_t add = mi_atomic_load_relaxed((_Atomic(int64_t)*)padd);\n  if (add != 0) {\n    mi_atomic(fetch_add_explicit)((_Atomic(int64_t)*)p, add, mi_memory_order(relaxed));\n  }\n}\nstatic inline void mi_atomic_maxi64_relaxed(volatile int64_t* p, int64_t x) {\n  int64_t current = mi_atomic_load_relaxed((_Atomic(int64_t)*)p);\n  while (current < x && !mi_atomic_cas_weak_release((_Atomic(int64_t)*)p, &current, x)) { /* nothing */ };\n}\n\n// Used by timers\n#define mi_atomic_loadi64_acquire(p)            mi_atomic(load_explicit)(p,mi_memory_order(acquire))\n#define mi_atomic_loadi64_relaxed(p)            mi_atomic(load_explicit)(p,mi_memory_order(relaxed))\n#define mi_atomic_storei64_release(p,x)         mi_atomic(store_explicit)(p,x,mi_memory_order(release))\n#define mi_atomic_storei64_relaxed(p,x)         mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed))\n\n#define mi_atomic_casi64_strong_acq_rel(p,e,d)  mi_atomic_cas_strong_acq_rel(p,e,d)\n#define mi_atomic_addi64_acq_rel(p,i)           mi_atomic_add_acq_rel(p,i)\n\n\n#elif defined(_MSC_VER)\n\n// Legacy MSVC plain C compilation wrapper that uses Interlocked operations to model C11 atomics.\n#include <intrin.h>\n#ifdef _WIN64\ntypedef LONG64   msc_intptr_t;\n#define MI_64(f) f##64\n#else\ntypedef LONG     msc_intptr_t;\n#define MI_64(f) f\n#endif\n\ntypedef enum mi_memory_order_e {\n  mi_memory_order_relaxed,\n  mi_memory_order_consume,\n  mi_memory_order_acquire,\n  mi_memory_order_release,\n  mi_memory_order_acq_rel,\n  mi_memory_order_seq_cst\n} mi_memory_order;\n\nstatic inline uintptr_t mi_atomic_fetch_add_explicit(_Atomic(uintptr_t)*p, uintptr_t add, mi_memory_order mo) {\n  (void)(mo);\n  return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, (msc_intptr_t)add);\n}\nstatic inline uintptr_t mi_atomic_fetch_sub_explicit(_Atomic(uintptr_t)*p, uintptr_t sub, mi_memory_order mo) {\n  (void)(mo);\n  return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, -((msc_intptr_t)sub));\n}\nstatic inline uintptr_t mi_atomic_fetch_and_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {\n  (void)(mo);\n  return (uintptr_t)MI_64(_InterlockedAnd)((volatile msc_intptr_t*)p, (msc_intptr_t)x);\n}\nstatic inline uintptr_t mi_atomic_fetch_or_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {\n  (void)(mo);\n  return (uintptr_t)MI_64(_InterlockedOr)((volatile msc_intptr_t*)p, (msc_intptr_t)x);\n}\nstatic inline bool mi_atomic_compare_exchange_strong_explicit(_Atomic(uintptr_t)*p, uintptr_t* expected, uintptr_t desired, mi_memory_order mo1, mi_memory_order mo2) {\n  (void)(mo1); (void)(mo2);\n  uintptr_t read = (uintptr_t)MI_64(_InterlockedCompareExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)desired, (msc_intptr_t)(*expected));\n  if (read == *expected) {\n    return true;\n  }\n  else {\n    *expected = read;\n    return false;\n  }\n}\nstatic inline bool mi_atomic_compare_exchange_weak_explicit(_Atomic(uintptr_t)*p, uintptr_t* expected, uintptr_t desired, mi_memory_order mo1, mi_memory_order mo2) {\n  return mi_atomic_compare_exchange_strong_explicit(p, expected, desired, mo1, mo2);\n}\nstatic inline uintptr_t mi_atomic_exchange_explicit(_Atomic(uintptr_t)*p, uintptr_t exchange, mi_memory_order mo) {\n  (void)(mo);\n  return (uintptr_t)MI_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange);\n}\nstatic inline void mi_atomic_thread_fence(mi_memory_order mo) {\n  (void)(mo);\n  _Atomic(uintptr_t) x = 0;\n  mi_atomic_exchange_explicit(&x, 1, mo);\n}\nstatic inline uintptr_t mi_atomic_load_explicit(_Atomic(uintptr_t) const* p, mi_memory_order mo) {\n  (void)(mo);\n#if defined(_M_IX86) || defined(_M_X64)\n  return *p;\n#else\n  uintptr_t x = *p;\n  if (mo > mi_memory_order_relaxed) {\n    while (!mi_atomic_compare_exchange_weak_explicit((_Atomic(uintptr_t)*)p, &x, x, mo, mi_memory_order_relaxed)) { /* nothing */ };\n  }\n  return x;\n#endif\n}\nstatic inline void mi_atomic_store_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {\n  (void)(mo);\n#if defined(_M_IX86) || defined(_M_X64)\n  *p = x;\n#else\n  mi_atomic_exchange_explicit(p, x, mo);\n#endif\n}\nstatic inline int64_t mi_atomic_loadi64_explicit(_Atomic(int64_t)*p, mi_memory_order mo) {\n  (void)(mo);\n#if defined(_M_X64)\n  return *p;\n#else\n  int64_t old = *p;\n  int64_t x = old;\n  while ((old = InterlockedCompareExchange64(p, x, old)) != x) {\n    x = old;\n  }\n  return x;\n#endif\n}\nstatic inline void mi_atomic_storei64_explicit(_Atomic(int64_t)*p, int64_t x, mi_memory_order mo) {\n  (void)(mo);\n#if defined(x_M_IX86) || defined(_M_X64)\n  *p = x;\n#else\n  InterlockedExchange64(p, x);\n#endif\n}\n\n// These are used by the statistics\nstatic inline int64_t mi_atomic_addi64_relaxed(volatile _Atomic(int64_t)*p, int64_t add) {\n#ifdef _WIN64\n  return (int64_t)mi_atomic_addi((int64_t*)p, add);\n#else\n  int64_t current;\n  int64_t sum;\n  do {\n    current = *p;\n    sum = current + add;\n  } while (_InterlockedCompareExchange64(p, sum, current) != current);\n  return current;\n#endif\n}\nstatic inline void mi_atomic_void_addi64_relaxed(volatile int64_t* p, const volatile int64_t* padd) {\n  const int64_t add = *padd;\n  if (add != 0) {\n    mi_atomic_addi64_relaxed((volatile _Atomic(int64_t)*)p, add);\n  }\n}\n\nstatic inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)*p, int64_t x) {\n  int64_t current;\n  do {\n    current = *p;\n  } while (current < x && _InterlockedCompareExchange64(p, x, current) != current);\n}\n\nstatic inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t*)p, int64_t i) {\n  mi_atomic_addi64_relaxed(p, i);\n}\n\nstatic inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p, int64_t* exp, int64_t des) {\n  int64_t read = _InterlockedCompareExchange64(p, des, *exp);\n  if (read == *exp) {\n    return true;\n  }\n  else {\n    *exp = read;\n    return false;\n  }\n}\n\n// The pointer macros cast to `uintptr_t`.\n#define mi_atomic_load_ptr_acquire(tp,p)                (tp*)mi_atomic_load_acquire((_Atomic(uintptr_t)*)(p))\n#define mi_atomic_load_ptr_relaxed(tp,p)                (tp*)mi_atomic_load_relaxed((_Atomic(uintptr_t)*)(p))\n#define mi_atomic_store_ptr_release(tp,p,x)             mi_atomic_store_release((_Atomic(uintptr_t)*)(p),(uintptr_t)(x))\n#define mi_atomic_store_ptr_relaxed(tp,p,x)             mi_atomic_store_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)(x))\n#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des)    mi_atomic_cas_weak_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)\n#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des)    mi_atomic_cas_weak_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)\n#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des)  mi_atomic_cas_strong_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)\n#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des)  mi_atomic_cas_strong_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)\n#define mi_atomic_exchange_ptr_relaxed(tp,p,x)          (tp*)mi_atomic_exchange_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)x)\n#define mi_atomic_exchange_ptr_release(tp,p,x)          (tp*)mi_atomic_exchange_release((_Atomic(uintptr_t)*)(p),(uintptr_t)x)\n#define mi_atomic_exchange_ptr_acq_rel(tp,p,x)          (tp*)mi_atomic_exchange_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t)x)\n\n#define mi_atomic_loadi64_acquire(p)    mi_atomic(loadi64_explicit)(p,mi_memory_order(acquire))\n#define mi_atomic_loadi64_relaxed(p)    mi_atomic(loadi64_explicit)(p,mi_memory_order(relaxed))\n#define mi_atomic_storei64_release(p,x) mi_atomic(storei64_explicit)(p,x,mi_memory_order(release))\n#define mi_atomic_storei64_relaxed(p,x) mi_atomic(storei64_explicit)(p,x,mi_memory_order(relaxed))\n\n\n#endif\n\n\n// Atomically add a signed value; returns the previous value.\nstatic inline intptr_t mi_atomic_addi(_Atomic(intptr_t)*p, intptr_t add) {\n  return (intptr_t)mi_atomic_add_acq_rel((_Atomic(uintptr_t)*)p, (uintptr_t)add);\n}\n\n// Atomically subtract a signed value; returns the previous value.\nstatic inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub) {\n  return (intptr_t)mi_atomic_addi(p, -sub);\n}\n\n\n// ----------------------------------------------------------------------\n// Once and Guard\n// ----------------------------------------------------------------------\n\ntypedef _Atomic(uintptr_t) mi_atomic_once_t;\n\n// Returns true only on the first invocation\nstatic inline bool mi_atomic_once( mi_atomic_once_t* once ) {\n  if (mi_atomic_load_relaxed(once) != 0) return false;     // quick test\n  uintptr_t expected = 0;\n  return mi_atomic_cas_strong_acq_rel(once, &expected, (uintptr_t)1); // try to set to 1\n}\n\ntypedef _Atomic(uintptr_t) mi_atomic_guard_t;\n\n// Allows only one thread to execute at a time\n#define mi_atomic_guard(guard) \\\n  uintptr_t _mi_guard_expected = 0; \\\n  for(bool _mi_guard_once = true; \\\n      _mi_guard_once && mi_atomic_cas_strong_acq_rel(guard,&_mi_guard_expected,(uintptr_t)1); \\\n      (mi_atomic_store_release(guard,(uintptr_t)0), _mi_guard_once = false) )\n\n\n\n// ----------------------------------------------------------------------\n// Yield\n// ----------------------------------------------------------------------\n\n#if defined(__cplusplus)\n#include <thread>\nstatic inline void mi_atomic_yield(void) {\n  std::this_thread::yield();\n}\n#elif defined(_WIN32)\nstatic inline void mi_atomic_yield(void) {\n  YieldProcessor();\n}\n#elif defined(__SSE2__)\n#include <emmintrin.h>\nstatic inline void mi_atomic_yield(void) {\n  _mm_pause();\n}\n#elif (defined(__GNUC__) || defined(__clang__)) && \\\n      (defined(__x86_64__) || defined(__i386__) || \\\n       defined(__aarch64__) || defined(__arm__) || \\\n       defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__))\n#if defined(__x86_64__) || defined(__i386__)\nstatic inline void mi_atomic_yield(void) {\n  __asm__ volatile (\"pause\" ::: \"memory\");\n}\n#elif defined(__aarch64__)\nstatic inline void mi_atomic_yield(void) {\n  __asm__ volatile(\"wfe\");\n}\n#elif defined(__arm__)\n#if __ARM_ARCH >= 7\nstatic inline void mi_atomic_yield(void) {\n  __asm__ volatile(\"yield\" ::: \"memory\");\n}\n#else\nstatic inline void mi_atomic_yield(void) {\n  __asm__ volatile (\"nop\" ::: \"memory\");\n}\n#endif\n#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__)\n#ifdef __APPLE__\nstatic inline void mi_atomic_yield(void) {\n  __asm__ volatile (\"or r27,r27,r27\" ::: \"memory\");\n}\n#else\nstatic inline void mi_atomic_yield(void) {\n  __asm__ __volatile__ (\"or 27,27,27\" ::: \"memory\");\n}\n#endif\n#endif\n#elif defined(__sun)\n// Fallback for other archs\n#include <synch.h>\nstatic inline void mi_atomic_yield(void) {\n  smt_pause();\n}\n#elif defined(__wasi__)\n#include <sched.h>\nstatic inline void mi_atomic_yield(void) {\n  sched_yield();\n}\n#else\n#include <unistd.h>\nstatic inline void mi_atomic_yield(void) {\n  sleep(0);\n}\n#endif\n\n\n// ----------------------------------------------------------------------\n// Locks \n// These do not have to be recursive and should be light-weight \n// in-process only locks. Only used for reserving arena's and to \n// maintain the abandoned list.\n// ----------------------------------------------------------------------\n#if _MSC_VER\n#pragma warning(disable:26110)  // unlock with holding lock\n#endif\n\n#define mi_lock(lock)    for(bool _go = (mi_lock_acquire(lock),true); _go; (mi_lock_release(lock), _go=false) )\n\n#if defined(_WIN32)\n\n#if 1\n#define mi_lock_t  SRWLOCK   // slim reader-writer lock\n\nstatic inline bool mi_lock_try_acquire(mi_lock_t* lock) {\n  return TryAcquireSRWLockExclusive(lock);\n}\nstatic inline void mi_lock_acquire(mi_lock_t* lock) {\n  AcquireSRWLockExclusive(lock);\n}\nstatic inline void mi_lock_release(mi_lock_t* lock) {\n  ReleaseSRWLockExclusive(lock);\n}\nstatic inline void mi_lock_init(mi_lock_t* lock) {\n  InitializeSRWLock(lock);\n}\nstatic inline void mi_lock_done(mi_lock_t* lock) {\n  (void)(lock);\n}\n\n#else\n#define mi_lock_t  CRITICAL_SECTION\n\nstatic inline bool mi_lock_try_acquire(mi_lock_t* lock) {\n  return TryEnterCriticalSection(lock);\n}\nstatic inline void mi_lock_acquire(mi_lock_t* lock) {\n  EnterCriticalSection(lock);\n}\nstatic inline void mi_lock_release(mi_lock_t* lock) {\n  LeaveCriticalSection(lock);\n}\nstatic inline void mi_lock_init(mi_lock_t* lock) {\n  InitializeCriticalSection(lock);\n}\nstatic inline void mi_lock_done(mi_lock_t* lock) {\n  DeleteCriticalSection(lock);\n}\n\n#endif\n\n#elif defined(MI_USE_PTHREADS)\n\nvoid _mi_error_message(int err, const char* fmt, ...);\n\n#define mi_lock_t  pthread_mutex_t\n\nstatic inline bool mi_lock_try_acquire(mi_lock_t* lock) {\n  return (pthread_mutex_trylock(lock) == 0);\n}\nstatic inline void mi_lock_acquire(mi_lock_t* lock) {\n  const int err = pthread_mutex_lock(lock);\n  if (err != 0) {\n    _mi_error_message(err, \"internal error: lock cannot be acquired\\n\");\n  }\n}\nstatic inline void mi_lock_release(mi_lock_t* lock) {\n  pthread_mutex_unlock(lock);\n}\nstatic inline void mi_lock_init(mi_lock_t* lock) {\n  pthread_mutex_init(lock, NULL);\n}\nstatic inline void mi_lock_done(mi_lock_t* lock) {\n  pthread_mutex_destroy(lock);\n}\n\n#elif defined(__cplusplus)\n\n#include <mutex>\n#define mi_lock_t  std::mutex\n\nstatic inline bool mi_lock_try_acquire(mi_lock_t* lock) {\n  return lock->try_lock();\n}\nstatic inline void mi_lock_acquire(mi_lock_t* lock) {\n  lock->lock();\n}\nstatic inline void mi_lock_release(mi_lock_t* lock) {\n  lock->unlock();\n}\nstatic inline void mi_lock_init(mi_lock_t* lock) {\n  (void)(lock);\n}\nstatic inline void mi_lock_done(mi_lock_t* lock) {\n  (void)(lock);\n}\n\n#else\n\n// fall back to poor man's locks.\n// this should only be the case in a single-threaded environment (like __wasi__)\n\n#define mi_lock_t  _Atomic(uintptr_t)\n\nstatic inline bool mi_lock_try_acquire(mi_lock_t* lock) {\n  uintptr_t expected = 0;\n  return mi_atomic_cas_strong_acq_rel(lock, &expected, (uintptr_t)1);\n}\nstatic inline void mi_lock_acquire(mi_lock_t* lock) {\n  for (int i = 0; i < 1000; i++) {  // for at most 1000 tries?\n    if (mi_lock_try_acquire(lock)) return;\n    mi_atomic_yield();\n  }\n}\nstatic inline void mi_lock_release(mi_lock_t* lock) {\n  mi_atomic_store_release(lock, (uintptr_t)0);\n}\nstatic inline void mi_lock_init(mi_lock_t* lock) {\n  mi_lock_release(lock);\n}\nstatic inline void mi_lock_done(mi_lock_t* lock) {\n  (void)(lock);\n}\n\n#endif\n\n\n#endif // __MIMALLOC_ATOMIC_H\n"
  },
  {
    "path": "include/mimalloc/internal.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_INTERNAL_H\n#define MIMALLOC_INTERNAL_H\n\n// --------------------------------------------------------------------------\n// This file contains the internal API's of mimalloc and various utility\n// functions and macros.\n// --------------------------------------------------------------------------\n\n#include \"types.h\"\n#include \"track.h\"\n\n\n// --------------------------------------------------------------------------\n// Compiler defines\n// --------------------------------------------------------------------------\n\n#if (MI_DEBUG>0)\n#define mi_trace_message(...)  _mi_trace_message(__VA_ARGS__)\n#else\n#define mi_trace_message(...)\n#endif\n\n#define mi_decl_cache_align     mi_decl_align(64)\n\n#if defined(_MSC_VER)\n#pragma warning(disable:4127)   // suppress constant conditional warning (due to MI_SECURE paths)\n#pragma warning(disable:26812)  // unscoped enum warning\n#define mi_decl_noinline        __declspec(noinline)\n#define mi_decl_thread          __declspec(thread)\n#define mi_decl_align(a)        __declspec(align(a))\n#define mi_decl_noreturn        __declspec(noreturn)\n#define mi_decl_weak\n#define mi_decl_hidden\n#define mi_decl_cold\n#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) // includes clang and icc\n#define mi_decl_noinline        __attribute__((noinline))\n#define mi_decl_thread          __thread\n#define mi_decl_align(a)        __attribute__((aligned(a)))\n#define mi_decl_noreturn        __attribute__((noreturn))\n#define mi_decl_weak            __attribute__((weak))\n#define mi_decl_hidden          __attribute__((visibility(\"hidden\")))\n#if (__GNUC__ >= 4) || defined(__clang__)\n#define mi_decl_cold            __attribute__((cold))\n#else\n#define mi_decl_cold\n#endif\n#elif __cplusplus >= 201103L    // c++11\n#define mi_decl_noinline\n#define mi_decl_thread          thread_local\n#define mi_decl_align(a)        alignas(a)\n#define mi_decl_noreturn        [[noreturn]]\n#define mi_decl_weak\n#define mi_decl_hidden\n#define mi_decl_cold\n#else\n#define mi_decl_noinline\n#define mi_decl_thread          __thread        // hope for the best :-)\n#define mi_decl_align(a)\n#define mi_decl_noreturn\n#define mi_decl_weak\n#define mi_decl_hidden\n#define mi_decl_cold\n#endif\n\n#if defined(__GNUC__) || defined(__clang__)\n#define mi_unlikely(x)     (__builtin_expect(!!(x),false))\n#define mi_likely(x)       (__builtin_expect(!!(x),true))\n#elif (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)\n#define mi_unlikely(x)     (x) [[unlikely]]\n#define mi_likely(x)       (x) [[likely]]\n#else\n#define mi_unlikely(x)     (x)\n#define mi_likely(x)       (x)\n#endif\n\n#ifndef __has_builtin\n#define __has_builtin(x)    0\n#endif\n\n#if defined(__cplusplus)\n#define mi_decl_externc     extern \"C\"\n#else\n#define mi_decl_externc\n#endif\n\n#if defined(__EMSCRIPTEN__) && !defined(__wasi__)\n#define __wasi__\n#endif\n\n\n// --------------------------------------------------------------------------\n// Internal functions\n// --------------------------------------------------------------------------\n\n// \"libc.c\"\n#include    <stdarg.h>\nint         _mi_vsnprintf(char* buf, size_t bufsize, const char* fmt, va_list args);\nint         _mi_snprintf(char* buf, size_t buflen, const char* fmt, ...);\nchar        _mi_toupper(char c);\nint         _mi_strnicmp(const char* s, const char* t, size_t n);\nvoid        _mi_strlcpy(char* dest, const char* src, size_t dest_size);\nvoid        _mi_strlcat(char* dest, const char* src, size_t dest_size);\nsize_t      _mi_strlen(const char* s);\nsize_t      _mi_strnlen(const char* s, size_t max_len);\nbool        _mi_getenv(const char* name, char* result, size_t result_size);\n\n// \"options.c\"\nvoid        _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message);\nvoid        _mi_fprintf(mi_output_fun* out, void* arg, const char* fmt, ...);\nvoid        _mi_message(const char* fmt, ...);\nvoid        _mi_warning_message(const char* fmt, ...);\nvoid        _mi_verbose_message(const char* fmt, ...);\nvoid        _mi_trace_message(const char* fmt, ...);\nvoid        _mi_options_init(void);\nlong        _mi_option_get_fast(mi_option_t option);\nvoid        _mi_error_message(int err, const char* fmt, ...);\n\n// random.c\nvoid        _mi_random_init(mi_random_ctx_t* ctx);\nvoid        _mi_random_init_weak(mi_random_ctx_t* ctx);\nvoid        _mi_random_reinit_if_weak(mi_random_ctx_t * ctx);\nvoid        _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx);\nuintptr_t   _mi_random_next(mi_random_ctx_t* ctx);\nuintptr_t   _mi_heap_random_next(mi_heap_t* heap);\nuintptr_t   _mi_os_random_weak(uintptr_t extra_seed);\nstatic inline uintptr_t _mi_random_shuffle(uintptr_t x);\n\n// init.c\nextern mi_decl_hidden mi_decl_cache_align mi_stats_t       _mi_stats_main;\nextern mi_decl_hidden mi_decl_cache_align const mi_page_t  _mi_page_empty;\nvoid        _mi_auto_process_init(void);\nvoid mi_cdecl _mi_auto_process_done(void) mi_attr_noexcept;\nbool        _mi_is_redirected(void);\nbool        _mi_allocator_init(const char** message);\nvoid        _mi_allocator_done(void);\nbool        _mi_is_main_thread(void);\nsize_t      _mi_current_thread_count(void);\nbool        _mi_preloading(void);           // true while the C runtime is not initialized yet\nvoid        _mi_thread_done(mi_heap_t* heap);\nvoid        _mi_thread_data_collect(void);\nvoid        _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap);\nmi_threadid_t _mi_thread_id(void) mi_attr_noexcept;\nmi_heap_t*    _mi_heap_main_get(void);     // statically allocated main backing heap\nmi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id);\nvoid        _mi_heap_guarded_init(mi_heap_t* heap);\n\n// os.c\nvoid        _mi_os_init(void);                                            // called from process init\nvoid*       _mi_os_alloc(size_t size, mi_memid_t* memid);\nvoid*       _mi_os_zalloc(size_t size, mi_memid_t* memid);\nvoid        _mi_os_free(void* p, size_t size, mi_memid_t memid);\nvoid        _mi_os_free_ex(void* p, size_t size, bool still_committed, mi_memid_t memid);\n\nsize_t      _mi_os_page_size(void);\nsize_t      _mi_os_good_alloc_size(size_t size);\nbool        _mi_os_has_overcommit(void);\nbool        _mi_os_has_virtual_reserve(void);\n\nbool        _mi_os_reset(void* addr, size_t size);\nbool        _mi_os_decommit(void* addr, size_t size);\nbool        _mi_os_unprotect(void* addr, size_t size);\nbool        _mi_os_purge(void* p, size_t size);\nbool        _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size);\nvoid        _mi_os_reuse(void* p, size_t size);\nmi_decl_nodiscard bool _mi_os_commit(void* p, size_t size, bool* is_zero);\nmi_decl_nodiscard bool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size);\nbool        _mi_os_protect(void* addr, size_t size);\n\nvoid*       _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid);\nvoid*       _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_memid_t* memid);\n\nvoid*       _mi_os_get_aligned_hint(size_t try_alignment, size_t size);\nbool        _mi_os_canuse_large_page(size_t size, size_t alignment);\nsize_t      _mi_os_large_page_size(void);\nvoid*       _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_secs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid);\n\nint         _mi_os_numa_node_count(void);\nint         _mi_os_numa_node(void);\n\n// arena.c\nmi_arena_id_t _mi_arena_id_none(void);\nvoid        _mi_arena_free(void* p, size_t size, size_t still_committed_size, mi_memid_t memid);\nvoid*       _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid);\nvoid*       _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid);\nbool        _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id);\nbool        _mi_arena_contains(const void* p);\nvoid        _mi_arenas_collect(bool force_purge);\nvoid        _mi_arena_unsafe_destroy_all(void);\n\nbool        _mi_arena_segment_clear_abandoned(mi_segment_t* segment);\nvoid        _mi_arena_segment_mark_abandoned(mi_segment_t* segment);\n\nvoid*       _mi_arena_meta_zalloc(size_t size, mi_memid_t* memid);\nvoid        _mi_arena_meta_free(void* p, mi_memid_t memid, size_t size);\n\ntypedef struct mi_arena_field_cursor_s { // abstract struct\n  size_t         os_list_count;           // max entries to visit in the OS abandoned list\n  size_t         start;                   // start arena idx (may need to be wrapped)\n  size_t         end;                     // end arena idx (exclusive, may need to be wrapped)\n  size_t         bitmap_idx;              // current bit idx for an arena\n  mi_subproc_t*  subproc;                 // only visit blocks in this sub-process\n  bool           visit_all;               // ensure all abandoned blocks are seen (blocking)\n  bool           hold_visit_lock;         // if the subproc->abandoned_os_visit_lock is held\n} mi_arena_field_cursor_t;\nvoid          _mi_arena_field_cursor_init(mi_heap_t* heap, mi_subproc_t* subproc, bool visit_all, mi_arena_field_cursor_t* current);\nmi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_field_cursor_t* previous);\nvoid          _mi_arena_field_cursor_done(mi_arena_field_cursor_t* current);\n\n// \"segment-map.c\"\nvoid        _mi_segment_map_allocated_at(const mi_segment_t* segment);\nvoid        _mi_segment_map_freed_at(const mi_segment_t* segment);\nvoid        _mi_segment_map_unsafe_destroy(void);\n\n// \"segment.c\"\nmi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment, mi_segments_tld_t* tld);\nvoid       _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld);\nvoid       _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld);\nbool       _mi_segment_try_reclaim_abandoned( mi_heap_t* heap, bool try_all, mi_segments_tld_t* tld);\nvoid       _mi_segment_collect(mi_segment_t* segment, bool force);\n\n#if MI_HUGE_PAGE_ABANDON\nvoid        _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block);\n#else\nvoid        _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block);\n#endif\n\nuint8_t*   _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size); // page start for any page\nvoid       _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld);\nvoid       _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld);\nbool       _mi_segment_attempt_reclaim(mi_heap_t* heap, mi_segment_t* segment);\nbool       _mi_segment_visit_blocks(mi_segment_t* segment, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);\n\n// \"page.c\"\nvoid*       _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, size_t* usable)  mi_attr_noexcept mi_attr_malloc;\n\nvoid        _mi_page_retire(mi_page_t* page) mi_attr_noexcept;                  // free the page if there are no other pages with many free blocks\nvoid        _mi_page_unfull(mi_page_t* page);\nvoid        _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force);   // free the page\nvoid        _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq);            // abandon the page, to be picked up by another thread...\nvoid        _mi_page_force_abandon(mi_page_t* page);\n\nvoid        _mi_heap_delayed_free_all(mi_heap_t* heap);\nbool        _mi_heap_delayed_free_partial(mi_heap_t* heap);\nvoid        _mi_heap_collect_retired(mi_heap_t* heap, bool force);\n\nvoid        _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never);\nbool        _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never);\nsize_t      _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append);\nvoid        _mi_deferred_free(mi_heap_t* heap, bool force);\n\nvoid        _mi_page_free_collect(mi_page_t* page,bool force);\nvoid        _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page);   // callback from segments\n\nsize_t      _mi_page_stats_bin(const mi_page_t* page); // for stats\nsize_t      _mi_bin_size(size_t bin);                  // for stats\nsize_t      _mi_bin(size_t size);                      // for stats\n\n// \"heap.c\"\nvoid        _mi_heap_init(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool noreclaim, uint8_t tag);\nvoid        _mi_heap_destroy_pages(mi_heap_t* heap);\nvoid        _mi_heap_collect_abandon(mi_heap_t* heap);\nvoid        _mi_heap_set_default_direct(mi_heap_t* heap);\nbool        _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid);\nvoid        _mi_heap_unsafe_destroy_all(mi_heap_t* heap);\nmi_heap_t*  _mi_heap_by_tag(mi_heap_t* heap, uint8_t tag);\nvoid        _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page);\nbool        _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi_block_visit_fun* visitor, void* arg);\n\n// \"stats.c\"\nvoid        _mi_stats_done(mi_stats_t* stats);\nvoid        _mi_stats_merge_thread(mi_tld_t* tld);\nmi_msecs_t  _mi_clock_now(void);\nmi_msecs_t  _mi_clock_end(mi_msecs_t start);\nmi_msecs_t  _mi_clock_start(void);\n\n// \"alloc.c\"\nvoid*       _mi_page_malloc_zero(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero, size_t* usable) mi_attr_noexcept;  // called from `_mi_malloc_generic`\nvoid*       _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept;                  // called from `_mi_heap_malloc_aligned`\nvoid*       _mi_page_malloc_zeroed(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept;           // called from `_mi_heap_malloc_aligned`\nvoid*       _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept;\nvoid*       _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, size_t* usable) mi_attr_noexcept;     // called from `_mi_heap_malloc_aligned`\nvoid*       _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero, size_t* usable_pre, size_t* usable_post) mi_attr_noexcept;\nmi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p);\nbool        _mi_free_delayed_block(mi_block_t* block);\nvoid        _mi_free_generic(mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept;  // for runtime integration\nvoid        _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size);\n\n#if MI_DEBUG>1\nbool        _mi_page_is_valid(mi_page_t* page);\n#endif\n\n\n/* -----------------------------------------------------------\n  Error codes passed to `_mi_fatal_error`\n  All are recoverable but EFAULT is a serious error and aborts by default in secure mode.\n  For portability define undefined error codes using common Unix codes:\n  <https://www-numi.fnal.gov/offline_software/srt_public_context/WebDocs/Errors/unix_system_errors.html>\n----------------------------------------------------------- */\n#include <errno.h>\n#ifndef EAGAIN         // double free\n#define EAGAIN (11)\n#endif\n#ifndef ENOMEM         // out of memory\n#define ENOMEM (12)\n#endif\n#ifndef EFAULT         // corrupted free-list or meta-data\n#define EFAULT (14)\n#endif\n#ifndef EINVAL         // trying to free an invalid pointer\n#define EINVAL (22)\n#endif\n#ifndef EOVERFLOW      // count*size overflow\n#define EOVERFLOW (75)\n#endif\n\n\n// ------------------------------------------------------\n// Assertions\n// ------------------------------------------------------\n\n#if (MI_DEBUG)\n// use our own assertion to print without memory allocation\nmi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func) mi_attr_noexcept;\n#define mi_assert(expr)     ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))\n#else\n#define mi_assert(x)\n#endif\n\n#if (MI_DEBUG>1)\n#define mi_assert_internal    mi_assert\n#else\n#define mi_assert_internal(x)\n#endif\n\n#if (MI_DEBUG>2)\n#define mi_assert_expensive   mi_assert\n#else\n#define mi_assert_expensive(x)\n#endif\n\n\n\n/* -----------------------------------------------------------\n  Inlined definitions\n----------------------------------------------------------- */\n#define MI_UNUSED(x)     (void)(x)\n#if (MI_DEBUG>0)\n#define MI_UNUSED_RELEASE(x)\n#else\n#define MI_UNUSED_RELEASE(x)  MI_UNUSED(x)\n#endif\n\n#define MI_INIT4(x)   x(),x(),x(),x()\n#define MI_INIT8(x)   MI_INIT4(x),MI_INIT4(x)\n#define MI_INIT16(x)  MI_INIT8(x),MI_INIT8(x)\n#define MI_INIT32(x)  MI_INIT16(x),MI_INIT16(x)\n#define MI_INIT64(x)  MI_INIT32(x),MI_INIT32(x)\n#define MI_INIT128(x) MI_INIT64(x),MI_INIT64(x)\n#define MI_INIT256(x) MI_INIT128(x),MI_INIT128(x)\n#define MI_INIT74(x)  MI_INIT64(x),MI_INIT8(x),x(),x()\n\n#include <string.h>\n// initialize a local variable to zero; use memset as compilers optimize constant sized memset's\n#define _mi_memzero_var(x)  memset(&x,0,sizeof(x))\n\n// Is `x` a power of two? (0 is considered a power of two)\nstatic inline bool _mi_is_power_of_two(uintptr_t x) {\n  return ((x & (x - 1)) == 0);\n}\n\n// Is a pointer aligned?\nstatic inline bool _mi_is_aligned(void* p, size_t alignment) {\n  mi_assert_internal(alignment != 0);\n  return (((uintptr_t)p % alignment) == 0);\n}\n\n// Align upwards\nstatic inline uintptr_t _mi_align_up(uintptr_t sz, size_t alignment) {\n  mi_assert_internal(alignment != 0);\n  uintptr_t mask = alignment - 1;\n  if ((alignment & mask) == 0) {  // power of two?\n    return ((sz + mask) & ~mask);\n  }\n  else {\n    return (((sz + mask)/alignment)*alignment);\n  }\n}\n\n// Align downwards\nstatic inline uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) {\n  mi_assert_internal(alignment != 0);\n  uintptr_t mask = alignment - 1;\n  if ((alignment & mask) == 0) { // power of two?\n    return (sz & ~mask);\n  }\n  else {\n    return ((sz / alignment) * alignment);\n  }\n}\n\n// Align a pointer upwards\nstatic inline void* mi_align_up_ptr(void* p, size_t alignment) {\n  return (void*)_mi_align_up((uintptr_t)p, alignment);\n}\n\n// Align a pointer downwards\nstatic inline void* mi_align_down_ptr(void* p, size_t alignment) {\n  return (void*)_mi_align_down((uintptr_t)p, alignment);\n}\n\n\n// Divide upwards: `s <= _mi_divide_up(s,d)*d < s+d`.\nstatic inline uintptr_t _mi_divide_up(uintptr_t size, size_t divider) {\n  mi_assert_internal(divider != 0);\n  return (divider == 0 ? size : ((size + divider - 1) / divider));\n}\n\n\n// clamp an integer\nstatic inline size_t _mi_clamp(size_t sz, size_t min, size_t max) {\n  if (sz < min) return min;\n  else if (sz > max) return max;\n  else return sz;\n}\n\n// Is memory zero initialized?\nstatic inline bool mi_mem_is_zero(const void* p, size_t size) {\n  for (size_t i = 0; i < size; i++) {\n    if (((uint8_t*)p)[i] != 0) return false;\n  }\n  return true;\n}\n\n\n// Align a byte size to a size in _machine words_,\n// i.e. byte size == `wsize*sizeof(void*)`.\nstatic inline size_t _mi_wsize_from_size(size_t size) {\n  mi_assert_internal(size <= SIZE_MAX - sizeof(uintptr_t));\n  return (size + sizeof(uintptr_t) - 1) / sizeof(uintptr_t);\n}\n\n// Overflow detecting multiply\n#if __has_builtin(__builtin_umul_overflow) || (defined(__GNUC__) && (__GNUC__ >= 5))\n#include <limits.h>      // UINT_MAX, ULONG_MAX\n#if defined(_CLOCK_T)    // for Illumos\n#undef _CLOCK_T\n#endif\nstatic inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) {\n  #if (SIZE_MAX == ULONG_MAX)\n    return __builtin_umull_overflow(count, size, (unsigned long *)total);\n  #elif (SIZE_MAX == UINT_MAX)\n    return __builtin_umul_overflow(count, size, (unsigned int *)total);\n  #else\n    return __builtin_umulll_overflow(count, size, (unsigned long long *)total);\n  #endif\n}\n#else /* __builtin_umul_overflow is unavailable */\nstatic inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) {\n  #define MI_MUL_COULD_OVERFLOW ((size_t)1 << (4*sizeof(size_t)))  // sqrt(SIZE_MAX)\n  *total = count * size;\n  // note: gcc/clang optimize this to directly check the overflow flag\n  return ((size >= MI_MUL_COULD_OVERFLOW || count >= MI_MUL_COULD_OVERFLOW) && size > 0 && (SIZE_MAX / size) < count);\n}\n#endif\n\n// Safe multiply `count*size` into `total`; return `true` on overflow.\nstatic inline bool mi_count_size_overflow(size_t count, size_t size, size_t* total) {\n  if (count==1) {  // quick check for the case where count is one (common for C++ allocators)\n    *total = size;\n    return false;\n  }\n  else if mi_unlikely(mi_mul_overflow(count, size, total)) {\n    #if MI_DEBUG > 0\n    _mi_error_message(EOVERFLOW, \"allocation request is too large (%zu * %zu bytes)\\n\", count, size);\n    #endif\n    *total = SIZE_MAX;\n    return true;\n  }\n  else return false;\n}\n\n\n/*----------------------------------------------------------------------------------------\n  Heap functions\n------------------------------------------------------------------------------------------- */\n\nextern mi_decl_hidden const mi_heap_t _mi_heap_empty;  // read-only empty heap, initial value of the thread local default heap\n\nstatic inline bool mi_heap_is_backing(const mi_heap_t* heap) {\n  return (heap->tld->heap_backing == heap);\n}\n\nstatic inline bool mi_heap_is_initialized(mi_heap_t* heap) {\n  mi_assert_internal(heap != NULL);\n  return (heap != NULL && heap != &_mi_heap_empty);\n}\n\nstatic inline uintptr_t _mi_ptr_cookie(const void* p) {\n  extern mi_decl_hidden mi_heap_t _mi_heap_main;\n  mi_assert_internal(_mi_heap_main.cookie != 0);\n  return ((uintptr_t)p ^ _mi_heap_main.cookie);\n}\n\n/* -----------------------------------------------------------\n  Pages\n----------------------------------------------------------- */\n\nstatic inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) {\n  mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_SIZE));\n  const size_t idx = _mi_wsize_from_size(size);\n  mi_assert_internal(idx < MI_PAGES_DIRECT);\n  return heap->pages_free_direct[idx];\n}\n\n// Segment that contains the pointer\n// Large aligned blocks may be aligned at N*MI_SEGMENT_SIZE (inside a huge segment > MI_SEGMENT_SIZE),\n// and we need align \"down\" to the segment info which is `MI_SEGMENT_SIZE` bytes before it;\n// therefore we align one byte before `p`.\n// We check for NULL afterwards on 64-bit systems to improve codegen for `mi_free`.\nstatic inline mi_segment_t* _mi_ptr_segment(const void* p) {\n  mi_segment_t* const segment = (mi_segment_t*)(((uintptr_t)p - 1) & ~MI_SEGMENT_MASK);\n  #if MI_INTPTR_SIZE <= 4\n  return (p==NULL ? NULL : segment);\n  #else\n  return ((intptr_t)segment <= 0 ? NULL : segment);\n  #endif\n}\n\nstatic inline mi_page_t* mi_slice_to_page(mi_slice_t* s) {\n  mi_assert_internal(s->slice_offset== 0 && s->slice_count > 0);\n  return (mi_page_t*)(s);\n}\n\nstatic inline mi_slice_t* mi_page_to_slice(mi_page_t* p) {\n  mi_assert_internal(p->slice_offset== 0 && p->slice_count > 0);\n  return (mi_slice_t*)(p);\n}\n\n// Segment belonging to a page\nstatic inline mi_segment_t* _mi_page_segment(const mi_page_t* page) {\n  mi_assert_internal(page!=NULL);\n  mi_segment_t* segment = _mi_ptr_segment(page);\n  mi_assert_internal(segment == NULL || ((mi_slice_t*)page >= segment->slices && (mi_slice_t*)page < segment->slices + segment->slice_entries));\n  return segment;\n}\n\nstatic inline mi_slice_t* mi_slice_first(const mi_slice_t* slice) {\n  mi_slice_t* start = (mi_slice_t*)((uint8_t*)slice - slice->slice_offset);\n  mi_assert_internal(start >= _mi_ptr_segment(slice)->slices);\n  mi_assert_internal(start->slice_offset == 0);\n  mi_assert_internal(start + start->slice_count > slice);\n  return start;\n}\n\n// Get the page containing the pointer (performance critical as it is called in mi_free)\nstatic inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) {\n  mi_assert_internal(p > (void*)segment);\n  ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment;\n  mi_assert_internal(diff > 0 && diff <= (ptrdiff_t)MI_SEGMENT_SIZE);\n  size_t idx = (size_t)diff >> MI_SEGMENT_SLICE_SHIFT;\n  mi_assert_internal(idx <= segment->slice_entries);\n  mi_slice_t* slice0 = (mi_slice_t*)&segment->slices[idx];\n  mi_slice_t* slice = mi_slice_first(slice0);  // adjust to the block that holds the page data\n  mi_assert_internal(slice->slice_offset == 0);\n  mi_assert_internal(slice >= segment->slices && slice < segment->slices + segment->slice_entries);\n  return mi_slice_to_page(slice);\n}\n\n// Quick page start for initialized pages\nstatic inline uint8_t* mi_page_start(const mi_page_t* page) {\n  mi_assert_internal(page->page_start != NULL);\n  mi_assert_expensive(_mi_segment_page_start(_mi_page_segment(page),page,NULL) == page->page_start);\n  return page->page_start;\n}\n\n// Get the page containing the pointer\nstatic inline mi_page_t* _mi_ptr_page(void* p) {\n  mi_assert_internal(p!=NULL);\n  return _mi_segment_page_of(_mi_ptr_segment(p), p);\n}\n\n// Get the block size of a page (special case for huge objects)\nstatic inline size_t mi_page_block_size(const mi_page_t* page) {\n  mi_assert_internal(page->block_size > 0);\n  return page->block_size;\n}\n\nstatic inline bool mi_page_is_huge(const mi_page_t* page) {\n  mi_assert_internal((page->is_huge && _mi_page_segment(page)->kind == MI_SEGMENT_HUGE) ||\n                     (!page->is_huge && _mi_page_segment(page)->kind != MI_SEGMENT_HUGE));\n  return page->is_huge;\n}\n\n// Get the usable block size of a page without fixed padding.\n// This may still include internal padding due to alignment and rounding up size classes.\nstatic inline size_t mi_page_usable_block_size(const mi_page_t* page) {\n  return mi_page_block_size(page) - MI_PADDING_SIZE;\n}\n\n// size of a segment\nstatic inline size_t mi_segment_size(mi_segment_t* segment) {\n  return segment->segment_slices * MI_SEGMENT_SLICE_SIZE;\n}\n\nstatic inline uint8_t* mi_segment_end(mi_segment_t* segment) {\n  return (uint8_t*)segment + mi_segment_size(segment);\n}\n\n// Thread free access\nstatic inline mi_block_t* mi_page_thread_free(const mi_page_t* page) {\n  return (mi_block_t*)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xthread_free) & ~3);\n}\n\nstatic inline mi_delayed_t mi_page_thread_free_flag(const mi_page_t* page) {\n  return (mi_delayed_t)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xthread_free) & 3);\n}\n\n// Heap access\nstatic inline mi_heap_t* mi_page_heap(const mi_page_t* page) {\n  return (mi_heap_t*)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xheap));\n}\n\nstatic inline void mi_page_set_heap(mi_page_t* page, mi_heap_t* heap) {\n  mi_assert_internal(mi_page_thread_free_flag(page) != MI_DELAYED_FREEING);\n  mi_atomic_store_release(&page->xheap,(uintptr_t)heap);\n  if (heap != NULL) { page->heap_tag = heap->tag; }\n}\n\n// Thread free flag helpers\nstatic inline mi_block_t* mi_tf_block(mi_thread_free_t tf) {\n  return (mi_block_t*)(tf & ~0x03);\n}\nstatic inline mi_delayed_t mi_tf_delayed(mi_thread_free_t tf) {\n  return (mi_delayed_t)(tf & 0x03);\n}\nstatic inline mi_thread_free_t mi_tf_make(mi_block_t* block, mi_delayed_t delayed) {\n  return (mi_thread_free_t)((uintptr_t)block | (uintptr_t)delayed);\n}\nstatic inline mi_thread_free_t mi_tf_set_delayed(mi_thread_free_t tf, mi_delayed_t delayed) {\n  return mi_tf_make(mi_tf_block(tf),delayed);\n}\nstatic inline mi_thread_free_t mi_tf_set_block(mi_thread_free_t tf, mi_block_t* block) {\n  return mi_tf_make(block, mi_tf_delayed(tf));\n}\n\n// are all blocks in a page freed?\n// note: needs up-to-date used count, (as the `xthread_free` list may not be empty). see `_mi_page_collect_free`.\nstatic inline bool mi_page_all_free(const mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  return (page->used == 0);\n}\n\n// are there any available blocks?\nstatic inline bool mi_page_has_any_available(const mi_page_t* page) {\n  mi_assert_internal(page != NULL && page->reserved > 0);\n  return (page->used < page->reserved || (mi_page_thread_free(page) != NULL));\n}\n\n// are there immediately available blocks, i.e. blocks available on the free list.\nstatic inline bool mi_page_immediate_available(const mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  return (page->free != NULL);\n}\n\n// is more than 7/8th of a page in use?\nstatic inline bool mi_page_is_mostly_used(const mi_page_t* page) {\n  if (page==NULL) return true;\n  uint16_t frac = page->reserved / 8U;\n  return (page->reserved - page->used <= frac);\n}\n\nstatic inline mi_page_queue_t* mi_page_queue(const mi_heap_t* heap, size_t size) {\n  return &((mi_heap_t*)heap)->pages[_mi_bin(size)];\n}\n\n\n\n//-----------------------------------------------------------\n// Page flags\n//-----------------------------------------------------------\nstatic inline bool mi_page_is_in_full(const mi_page_t* page) {\n  return page->flags.x.in_full;\n}\n\nstatic inline void mi_page_set_in_full(mi_page_t* page, bool in_full) {\n  page->flags.x.in_full = in_full;\n}\n\nstatic inline bool mi_page_has_aligned(const mi_page_t* page) {\n  return page->flags.x.has_aligned;\n}\n\nstatic inline void mi_page_set_has_aligned(mi_page_t* page, bool has_aligned) {\n  page->flags.x.has_aligned = has_aligned;\n}\n\n/* -------------------------------------------------------------------\n  Guarded objects\n------------------------------------------------------------------- */\n#if MI_GUARDED\nstatic inline bool mi_block_ptr_is_guarded(const mi_block_t* block, const void* p) {\n  const ptrdiff_t offset = (uint8_t*)p - (uint8_t*)block;\n  return (offset >= (ptrdiff_t)(sizeof(mi_block_t)) && block->next == MI_BLOCK_TAG_GUARDED);\n}\n\nstatic inline bool mi_heap_malloc_use_guarded(mi_heap_t* heap, size_t size) {\n  // this code is written to result in fast assembly as it is on the hot path for allocation\n  const size_t count = heap->guarded_sample_count - 1;  // if the rate was 0, this will underflow and count for a long time..\n  if mi_likely(count != 0) {\n    // no sample\n    heap->guarded_sample_count = count;\n    return false;\n  }\n  else if (size >= heap->guarded_size_min && size <= heap->guarded_size_max) {\n    // use guarded allocation\n    heap->guarded_sample_count = heap->guarded_sample_rate;  // reset\n    return (heap->guarded_sample_rate != 0);\n  }\n  else {\n    // failed size criteria, rewind count (but don't write to an empty heap)\n    if (heap->guarded_sample_rate != 0) { heap->guarded_sample_count = 1; }\n    return false;\n  }\n}\n\nmi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept;\n\n#endif\n\n\n/* -------------------------------------------------------------------\nEncoding/Decoding the free list next pointers\n\nThis is to protect against buffer overflow exploits where the\nfree list is mutated. Many hardened allocators xor the next pointer `p`\nwith a secret key `k1`, as `p^k1`. This prevents overwriting with known\nvalues but might be still too weak: if the attacker can guess\nthe pointer `p` this  can reveal `k1` (since `p^k1^p == k1`).\nMoreover, if multiple blocks can be read as well, the attacker can\nxor both as `(p1^k1) ^ (p2^k1) == p1^p2` which may reveal a lot\nabout the pointers (and subsequently `k1`).\n\nInstead mimalloc uses an extra key `k2` and encodes as `((p^k2)<<<k1)+k1`.\nSince these operations are not associative, the above approaches do not\nwork so well any more even if the `p` can be guesstimated. For example,\nfor the read case we can subtract two entries to discard the `+k1` term,\nbut that leads to `((p1^k2)<<<k1) - ((p2^k2)<<<k1)` at best.\nWe include the left-rotation since xor and addition are otherwise linear\nin the lowest bit. Finally, both keys are unique per page which reduces\nthe re-use of keys by a large factor.\n\nWe also pass a separate `null` value to be used as `NULL` or otherwise\n`(k2<<<k1)+k1` would appear (too) often as a sentinel value.\n------------------------------------------------------------------- */\n\nstatic inline bool mi_is_in_same_segment(const void* p, const void* q) {\n  return (_mi_ptr_segment(p) == _mi_ptr_segment(q));\n}\n\nstatic inline bool mi_is_in_same_page(const void* p, const void* q) {\n  mi_segment_t* segment = _mi_ptr_segment(p);\n  if (_mi_ptr_segment(q) != segment) return false;\n  // assume q may be invalid // return (_mi_segment_page_of(segment, p) == _mi_segment_page_of(segment, q));\n  mi_page_t* page = _mi_segment_page_of(segment, p);\n  size_t psize;\n  uint8_t* start = _mi_segment_page_start(segment, page, &psize);\n  return (start <= (uint8_t*)q && (uint8_t*)q < start + psize);\n}\n\nstatic inline uintptr_t mi_rotl(uintptr_t x, uintptr_t shift) {\n  shift %= MI_INTPTR_BITS;\n  return (shift==0 ? x : ((x << shift) | (x >> (MI_INTPTR_BITS - shift))));\n}\nstatic inline uintptr_t mi_rotr(uintptr_t x, uintptr_t shift) {\n  shift %= MI_INTPTR_BITS;\n  return (shift==0 ? x : ((x >> shift) | (x << (MI_INTPTR_BITS - shift))));\n}\n\nstatic inline void* mi_ptr_decode(const void* null, const mi_encoded_t x, const uintptr_t* keys) {\n  void* p = (void*)(mi_rotr(x - keys[0], keys[0]) ^ keys[1]);\n  return (p==null ? NULL : p);\n}\n\nstatic inline mi_encoded_t mi_ptr_encode(const void* null, const void* p, const uintptr_t* keys) {\n  uintptr_t x = (uintptr_t)(p==NULL ? null : p);\n  return mi_rotl(x ^ keys[1], keys[0]) + keys[0];\n}\n\nstatic inline uint32_t mi_ptr_encode_canary(const void* null, const void* p, const uintptr_t* keys) {\n  const uint32_t x = (uint32_t)(mi_ptr_encode(null,p,keys));\n  // make the lowest byte 0 to prevent spurious read overflows which could be a security issue (issue #951)\n  #ifdef MI_BIG_ENDIAN\n  return (x & 0x00FFFFFF);\n  #else\n  return (x & 0xFFFFFF00);\n  #endif\n}\n\nstatic inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* block, const uintptr_t* keys ) {\n  mi_track_mem_defined(block,sizeof(mi_block_t));\n  mi_block_t* next;\n  #ifdef MI_ENCODE_FREELIST\n  next = (mi_block_t*)mi_ptr_decode(null, block->next, keys);\n  #else\n  MI_UNUSED(keys); MI_UNUSED(null);\n  next = (mi_block_t*)block->next;\n  #endif\n  mi_track_mem_noaccess(block,sizeof(mi_block_t));\n  return next;\n}\n\nstatic inline void mi_block_set_nextx(const void* null, mi_block_t* block, const mi_block_t* next, const uintptr_t* keys) {\n  mi_track_mem_undefined(block,sizeof(mi_block_t));\n  #ifdef MI_ENCODE_FREELIST\n  block->next = mi_ptr_encode(null, next, keys);\n  #else\n  MI_UNUSED(keys); MI_UNUSED(null);\n  block->next = (mi_encoded_t)next;\n  #endif\n  mi_track_mem_noaccess(block,sizeof(mi_block_t));\n}\n\nstatic inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t* block) {\n  #ifdef MI_ENCODE_FREELIST\n  mi_block_t* next = mi_block_nextx(page,block,page->keys);\n  // check for free list corruption: is `next` at least in the same page?\n  // TODO: check if `next` is `page->block_size` aligned?\n  if mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next)) {\n    _mi_error_message(EFAULT, \"corrupted free list entry of size %zub at %p: value 0x%zx\\n\", mi_page_block_size(page), block, (uintptr_t)next);\n    next = NULL;\n  }\n  return next;\n  #else\n  MI_UNUSED(page);\n  return mi_block_nextx(page,block,NULL);\n  #endif\n}\n\nstatic inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, const mi_block_t* next) {\n  #ifdef MI_ENCODE_FREELIST\n  mi_block_set_nextx(page,block,next, page->keys);\n  #else\n  MI_UNUSED(page);\n  mi_block_set_nextx(page,block,next,NULL);\n  #endif\n}\n\n\n// -------------------------------------------------------------------\n// commit mask\n// -------------------------------------------------------------------\n\nstatic inline void mi_commit_mask_create_empty(mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    cm->mask[i] = 0;\n  }\n}\n\nstatic inline void mi_commit_mask_create_full(mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    cm->mask[i] = ~((size_t)0);\n  }\n}\n\nstatic inline bool mi_commit_mask_is_empty(const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    if (cm->mask[i] != 0) return false;\n  }\n  return true;\n}\n\nstatic inline bool mi_commit_mask_is_full(const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    if (cm->mask[i] != ~((size_t)0)) return false;\n  }\n  return true;\n}\n\n// defined in `segment.c`:\nsize_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total);\nsize_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx);\n\n#define mi_commit_mask_foreach(cm,idx,count) \\\n  idx = 0; \\\n  while ((count = _mi_commit_mask_next_run(cm,&idx)) > 0) {\n\n#define mi_commit_mask_foreach_end() \\\n    idx += count; \\\n  }\n\n\n\n/* -----------------------------------------------------------\n  memory id's\n----------------------------------------------------------- */\n\nstatic inline mi_memid_t _mi_memid_create(mi_memkind_t memkind) {\n  mi_memid_t memid;\n  _mi_memzero_var(memid);\n  memid.memkind = memkind;\n  return memid;\n}\n\nstatic inline mi_memid_t _mi_memid_none(void) {\n  return _mi_memid_create(MI_MEM_NONE);\n}\n\nstatic inline mi_memid_t _mi_memid_create_os(void* base, size_t size, bool committed, bool is_zero, bool is_large) {\n  mi_memid_t memid = _mi_memid_create(MI_MEM_OS);\n  memid.mem.os.base = base;\n  memid.mem.os.size = size;\n  memid.initially_committed = committed;\n  memid.initially_zero = is_zero;\n  memid.is_pinned = is_large;\n  return memid;\n}\n\n\n// -------------------------------------------------------------------\n// Fast \"random\" shuffle\n// -------------------------------------------------------------------\n\nstatic inline uintptr_t _mi_random_shuffle(uintptr_t x) {\n  if (x==0) { x = 17; }   // ensure we don't get stuck in generating zeros\n#if (MI_INTPTR_SIZE>=8)\n  // by Sebastiano Vigna, see: <http://xoshiro.di.unimi.it/splitmix64.c>\n  x ^= x >> 30;\n  x *= 0xbf58476d1ce4e5b9UL;\n  x ^= x >> 27;\n  x *= 0x94d049bb133111ebUL;\n  x ^= x >> 31;\n#elif (MI_INTPTR_SIZE==4)\n  // by Chris Wellons, see: <https://nullprogram.com/blog/2018/07/31/>\n  x ^= x >> 16;\n  x *= 0x7feb352dUL;\n  x ^= x >> 15;\n  x *= 0x846ca68bUL;\n  x ^= x >> 16;\n#endif\n  return x;\n}\n\n\n\n// -----------------------------------------------------------------------\n// Count bits: trailing or leading zeros (with MI_INTPTR_BITS on all zero)\n// -----------------------------------------------------------------------\n\n#if defined(__GNUC__)\n\n#include <limits.h>       // LONG_MAX\n#define MI_HAVE_FAST_BITSCAN\nstatic inline size_t mi_clz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  #if (SIZE_MAX == ULONG_MAX)\n    return __builtin_clzl(x);\n  #else\n    return __builtin_clzll(x);\n  #endif\n}\nstatic inline size_t mi_ctz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  #if (SIZE_MAX == ULONG_MAX)\n    return __builtin_ctzl(x);\n  #else\n    return __builtin_ctzll(x);\n  #endif\n}\n\n#elif defined(_MSC_VER)\n\n#include <limits.h>       // LONG_MAX\n#include <intrin.h>       // BitScanReverse64\n#define MI_HAVE_FAST_BITSCAN\nstatic inline size_t mi_clz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  unsigned long idx;\n  #if (SIZE_MAX == ULONG_MAX)\n    _BitScanReverse(&idx, x);\n  #else\n    _BitScanReverse64(&idx, x);\n  #endif\n  return ((MI_SIZE_BITS - 1) - (size_t)idx);\n}\nstatic inline size_t mi_ctz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  unsigned long idx;\n  #if (SIZE_MAX == ULONG_MAX)\n    _BitScanForward(&idx, x);\n  #else\n    _BitScanForward64(&idx, x);\n  #endif\n  return (size_t)idx;\n}\n\n#else\n\nstatic inline size_t mi_ctz_generic32(uint32_t x) {\n  // de Bruijn multiplication, see <http://supertech.csail.mit.edu/papers/debruijn.pdf>\n  static const uint8_t debruijn[32] = {\n    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,\n    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9\n  };\n  if (x==0) return 32;\n  return debruijn[(uint32_t)((x & -(int32_t)x) * (uint32_t)(0x077CB531U)) >> 27];\n}\n\nstatic inline size_t mi_clz_generic32(uint32_t x) {\n  // de Bruijn multiplication, see <http://supertech.csail.mit.edu/papers/debruijn.pdf>\n  static const uint8_t debruijn[32] = {\n    31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1,\n    23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0\n  };\n  if (x==0) return 32;\n  x |= x >> 1;\n  x |= x >> 2;\n  x |= x >> 4;\n  x |= x >> 8;\n  x |= x >> 16;\n  return debruijn[(uint32_t)(x * (uint32_t)(0x07C4ACDDU)) >> 27];\n}\n\nstatic inline size_t mi_ctz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  #if (MI_SIZE_BITS <= 32)\n    return mi_ctz_generic32((uint32_t)x);\n  #else\n    const uint32_t lo = (uint32_t)x;\n    if (lo != 0) {\n      return mi_ctz_generic32(lo);\n    }\n    else {\n      return (32 + mi_ctz_generic32((uint32_t)(x>>32)));\n    }\n  #endif\n}\n\nstatic inline size_t mi_clz(size_t x) {\n  if (x==0) return MI_SIZE_BITS;\n  #if (MI_SIZE_BITS <= 32)\n    return mi_clz_generic32((uint32_t)x);\n  #else\n    const uint32_t hi = (uint32_t)(x>>32);\n    if (hi != 0) {\n      return mi_clz_generic32(hi);\n    }\n    else {\n      return 32 + mi_clz_generic32((uint32_t)x);\n    }\n  #endif\n}\n\n#endif\n\n// \"bit scan reverse\": Return index of the highest bit (or MI_SIZE_BITS if `x` is zero)\nstatic inline size_t mi_bsr(size_t x) {\n  return (x==0 ? MI_SIZE_BITS : MI_SIZE_BITS - 1 - mi_clz(x));\n}\n\nsize_t _mi_popcount_generic(size_t x);\n\nstatic inline size_t mi_popcount(size_t x) {\n  if (x<=1) return x;\n  if (x==SIZE_MAX) return MI_SIZE_BITS;\n  #if defined(__GNUC__)\n    #if (SIZE_MAX == ULONG_MAX)\n      return __builtin_popcountl(x);\n    #else\n      return __builtin_popcountll(x);\n    #endif\n  #else\n    return _mi_popcount_generic(x);\n  #endif\n}\n\n// ---------------------------------------------------------------------------------\n// Provide our own `_mi_memcpy` for potential performance optimizations.\n//\n// For now, only on Windows with msvc/clang-cl we optimize to `rep movsb` if\n// we happen to run on x86/x64 cpu's that have \"fast short rep movsb\" (FSRM) support\n// (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017). See also issue #201 and pr #253.\n// ---------------------------------------------------------------------------------\n\n#if !MI_TRACK_ENABLED && defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))\n#include <intrin.h>\nextern mi_decl_hidden bool _mi_cpu_has_fsrm;\nextern mi_decl_hidden bool _mi_cpu_has_erms;\nstatic inline void _mi_memcpy(void* dst, const void* src, size_t n) {\n  if (_mi_cpu_has_fsrm && n <= 127) { // || (_mi_cpu_has_erms && n > 128)) {\n    __movsb((unsigned char*)dst, (const unsigned char*)src, n);\n  }\n  else {\n    memcpy(dst, src, n);\n  }\n}\nstatic inline void _mi_memzero(void* dst, size_t n) {\n  if (_mi_cpu_has_fsrm && n <= 127) { // || (_mi_cpu_has_erms && n > 128)) {\n    __stosb((unsigned char*)dst, 0, n);\n  }\n  else {\n    memset(dst, 0, n);\n  }\n}\n#else\nstatic inline void _mi_memcpy(void* dst, const void* src, size_t n) {\n  memcpy(dst, src, n);\n}\nstatic inline void _mi_memzero(void* dst, size_t n) {\n  memset(dst, 0, n);\n}\n#endif\n\n// -------------------------------------------------------------------------------\n// The `_mi_memcpy_aligned` can be used if the pointers are machine-word aligned\n// This is used for example in `mi_realloc`.\n// -------------------------------------------------------------------------------\n\n#if (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__)\n// On GCC/CLang we provide a hint that the pointers are word aligned.\nstatic inline void _mi_memcpy_aligned(void* dst, const void* src, size_t n) {\n  mi_assert_internal(((uintptr_t)dst % MI_INTPTR_SIZE == 0) && ((uintptr_t)src % MI_INTPTR_SIZE == 0));\n  void* adst = __builtin_assume_aligned(dst, MI_INTPTR_SIZE);\n  const void* asrc = __builtin_assume_aligned(src, MI_INTPTR_SIZE);\n  _mi_memcpy(adst, asrc, n);\n}\n\nstatic inline void _mi_memzero_aligned(void* dst, size_t n) {\n  mi_assert_internal((uintptr_t)dst % MI_INTPTR_SIZE == 0);\n  void* adst = __builtin_assume_aligned(dst, MI_INTPTR_SIZE);\n  _mi_memzero(adst, n);\n}\n#else\n// Default fallback on `_mi_memcpy`\nstatic inline void _mi_memcpy_aligned(void* dst, const void* src, size_t n) {\n  mi_assert_internal(((uintptr_t)dst % MI_INTPTR_SIZE == 0) && ((uintptr_t)src % MI_INTPTR_SIZE == 0));\n  _mi_memcpy(dst, src, n);\n}\n\nstatic inline void _mi_memzero_aligned(void* dst, size_t n) {\n  mi_assert_internal((uintptr_t)dst % MI_INTPTR_SIZE == 0);\n  _mi_memzero(dst, n);\n}\n#endif\n\n\n#endif\n"
  },
  {
    "path": "include/mimalloc/prim.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_PRIM_H\n#define MIMALLOC_PRIM_H\n#include \"internal.h\"             // mi_decl_hidden\n\n// --------------------------------------------------------------------------\n// This file specifies the primitive portability API.\n// Each OS/host needs to implement these primitives, see `src/prim`\n// for implementations on Window, macOS, WASI, and Linux/Unix.\n//\n// note: on all primitive functions, we always have result parameters != NULL, and:\n//  addr != NULL and page aligned\n//  size > 0     and page aligned\n//  the return value is an error code as an `int` where 0 is success\n// --------------------------------------------------------------------------\n\n// OS memory configuration\ntypedef struct mi_os_mem_config_s {\n  size_t  page_size;              // default to 4KiB\n  size_t  large_page_size;        // 0 if not supported, usually 2MiB (4MiB on Windows)\n  size_t  alloc_granularity;      // smallest allocation size (usually 4KiB, on Windows 64KiB)\n  size_t  physical_memory_in_kib; // physical memory size in KiB\n  size_t  virtual_address_bits;   // usually 48 or 56 bits on 64-bit systems. (used to determine secure randomization)\n  bool    has_overcommit;         // can we reserve more memory than can be actually committed?\n  bool    has_partial_free;       // can allocated blocks be freed partially? (true for mmap, false for VirtualAlloc)\n  bool    has_virtual_reserve;    // supports virtual address space reservation? (if true we can reserve virtual address space without using commit or physical memory)\n} mi_os_mem_config_t;\n\n// Initialize\nvoid _mi_prim_mem_init( mi_os_mem_config_t* config );\n\n// Free OS memory\nint _mi_prim_free(void* addr, size_t size );\n\n// Allocate OS memory. Return NULL on error.\n// The `try_alignment` is just a hint and the returned pointer does not have to be aligned.\n// If `commit` is false, the virtual memory range only needs to be reserved (with no access)\n// which will later be committed explicitly using `_mi_prim_commit`.\n// `is_zero` is set to true if the memory was zero initialized (as on most OS's)\n// The `hint_addr` address is either `NULL` or a preferred allocation address but can be ignored.\n// pre: !commit => !allow_large\n//      try_alignment >= _mi_os_page_size() and a power of 2\nint _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr);\n\n// Commit memory. Returns error code or 0 on success.\n// For example, on Linux this would make the memory PROT_READ|PROT_WRITE.\n// `is_zero` is set to true if the memory was zero initialized (e.g. on Windows)\nint _mi_prim_commit(void* addr, size_t size, bool* is_zero);\n\n// Decommit memory. Returns error code or 0 on success. The `needs_recommit` result is true\n// if the memory would need to be re-committed. For example, on Windows this is always true,\n// but on Linux we could use MADV_DONTNEED to decommit which does not need a recommit.\n// pre: needs_recommit != NULL\nint _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit);\n\n// Reset memory. The range keeps being accessible but the content might be reset to zero at any moment.\n// Returns error code or 0 on success.\nint _mi_prim_reset(void* addr, size_t size);\n\n// Reuse memory. This is called for memory that is already committed but\n// may have been reset (`_mi_prim_reset`) or decommitted (`_mi_prim_decommit`) where `needs_recommit` was false.\n// Returns error code or 0 on success. On most platforms this is a no-op.\nint _mi_prim_reuse(void* addr, size_t size);\n\n// Protect memory. Returns error code or 0 on success.\nint _mi_prim_protect(void* addr, size_t size, bool protect);\n\n// Allocate huge (1GiB) pages possibly associated with a NUMA node.\n// `is_zero` is set to true if the memory was zero initialized (as on most OS's)\n// pre: size > 0  and a multiple of 1GiB.\n//      numa_node is either negative (don't care), or a numa node number.\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr);\n\n// Return the current NUMA node\nsize_t _mi_prim_numa_node(void);\n\n// Return the number of logical NUMA nodes\nsize_t _mi_prim_numa_node_count(void);\n\n// Clock ticks\nmi_msecs_t _mi_prim_clock_now(void);\n\n// Return process information (only for statistics)\ntypedef struct mi_process_info_s {\n  mi_msecs_t  elapsed;\n  mi_msecs_t  utime;\n  mi_msecs_t  stime;\n  size_t      current_rss;\n  size_t      peak_rss;\n  size_t      current_commit;\n  size_t      peak_commit;\n  size_t      page_faults;\n} mi_process_info_t;\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo);\n\n// Default stderr output. (only for warnings etc. with verbose enabled)\n// msg != NULL && _mi_strlen(msg) > 0\nvoid _mi_prim_out_stderr( const char* msg );\n\n// Get an environment variable. (only for options)\n// name != NULL, result != NULL, result_size >= 64\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size);\n\n\n// Fill a buffer with strong randomness; return `false` on error or if\n// there is no strong randomization available.\nbool _mi_prim_random_buf(void* buf, size_t buf_len);\n\n// Called on the first thread start, and should ensure `_mi_thread_done` is called on thread termination.\nvoid _mi_prim_thread_init_auto_done(void);\n\n// Called on process exit and may take action to clean up resources associated with the thread auto done.\nvoid _mi_prim_thread_done_auto_done(void);\n\n// Called when the default heap for a thread changes\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap);\n\n\n//-------------------------------------------------------------------\n// Access to TLS (thread local storage) slots.\n// We need fast access to both a unique thread id (in `free.c:mi_free`) and\n// to a thread-local heap pointer (in `alloc.c:mi_malloc`).\n// To achieve this we use specialized code for various platforms.\n//-------------------------------------------------------------------\n\n// On some libc + platform combinations we can directly access a thread-local storage (TLS) slot.\n// The TLS layout depends on both the OS and libc implementation so we use specific tests for each main platform.\n// If you test on another platform and it works please send a PR :-)\n// see also https://akkadia.org/drepper/tls.pdf for more info on the TLS register.\n//\n// Note: we would like to prefer `__builtin_thread_pointer()` nowadays instead of using assembly,\n// but unfortunately we can not detect support reliably (see issue #883)\n// We also use it on Apple OS as we use a TLS slot for the default heap there.\n#if defined(__GNUC__) && ( \\\n           (defined(__GLIBC__)   && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \\\n        || (defined(__APPLE__)   && (defined(__x86_64__) || defined(__aarch64__) || defined(__POWERPC__))) \\\n        || (defined(__BIONIC__)  && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \\\n        || (defined(__FreeBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \\\n        || (defined(__OpenBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \\\n      )\n\n#define MI_HAS_TLS_SLOT    1\n\nstatic inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {\n  void* res;\n  const size_t ofs = (slot*sizeof(void*));\n  #if defined(__i386__)\n    __asm__(\"movl %%gs:%1, %0\" : \"=r\" (res) : \"m\" (*((void**)ofs)) : );  // x86 32-bit always uses GS\n  #elif defined(__APPLE__) && defined(__x86_64__)\n    __asm__(\"movq %%gs:%1, %0\" : \"=r\" (res) : \"m\" (*((void**)ofs)) : );  // x86_64 macOSX uses GS\n  #elif defined(__x86_64__) && (MI_INTPTR_SIZE==4)\n    __asm__(\"movl %%fs:%1, %0\" : \"=r\" (res) : \"m\" (*((void**)ofs)) : );  // x32 ABI\n  #elif defined(__x86_64__)\n    __asm__(\"movq %%fs:%1, %0\" : \"=r\" (res) : \"m\" (*((void**)ofs)) : );  // x86_64 Linux, BSD uses FS\n  #elif defined(__arm__)\n    void** tcb; MI_UNUSED(ofs);\n    __asm__ volatile (\"mrc p15, 0, %0, c13, c0, 3\\nbic %0, %0, #3\" : \"=r\" (tcb));\n    res = tcb[slot];\n  #elif defined(__aarch64__)\n    void** tcb; MI_UNUSED(ofs);\n    #if defined(__APPLE__) // M1, issue #343\n    __asm__ volatile (\"mrs %0, tpidrro_el0\\nbic %0, %0, #7\" : \"=r\" (tcb));\n    #else\n    __asm__ volatile (\"mrs %0, tpidr_el0\" : \"=r\" (tcb));\n    #endif\n    res = tcb[slot];\n  #elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781\n    MI_UNUSED(ofs);\n    res = pthread_getspecific(slot);\n  #endif\n  return res;\n}\n\n// setting a tls slot is only used on macOS for now\nstatic inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexcept {\n  const size_t ofs = (slot*sizeof(void*));\n  #if defined(__i386__)\n    __asm__(\"movl %1,%%gs:%0\" : \"=m\" (*((void**)ofs)) : \"rn\" (value) : );  // 32-bit always uses GS\n  #elif defined(__APPLE__) && defined(__x86_64__)\n    __asm__(\"movq %1,%%gs:%0\" : \"=m\" (*((void**)ofs)) : \"rn\" (value) : );  // x86_64 macOS uses GS\n  #elif defined(__x86_64__) && (MI_INTPTR_SIZE==4)\n    __asm__(\"movl %1,%%fs:%0\" : \"=m\" (*((void**)ofs)) : \"rn\" (value) : );  // x32 ABI\n  #elif defined(__x86_64__)\n    __asm__(\"movq %1,%%fs:%0\" : \"=m\" (*((void**)ofs)) : \"rn\" (value) : );  // x86_64 Linux, BSD uses FS\n  #elif defined(__arm__)\n    void** tcb; MI_UNUSED(ofs);\n    __asm__ volatile (\"mrc p15, 0, %0, c13, c0, 3\\nbic %0, %0, #3\" : \"=r\" (tcb));\n    tcb[slot] = value;\n  #elif defined(__aarch64__)\n    void** tcb; MI_UNUSED(ofs);\n    #if defined(__APPLE__) // M1, issue #343\n    __asm__ volatile (\"mrs %0, tpidrro_el0\\nbic %0, %0, #7\" : \"=r\" (tcb));\n    #else\n    __asm__ volatile (\"mrs %0, tpidr_el0\" : \"=r\" (tcb));\n    #endif\n    tcb[slot] = value;\n  #elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781\n    MI_UNUSED(ofs);\n    pthread_setspecific(slot, value);\n  #endif\n}\n\n#elif _WIN32 && MI_WIN_USE_FIXED_TLS && !defined(MI_WIN_USE_FLS)\n\n// On windows we can store the thread-local heap at a fixed TLS slot to avoid\n// thread-local initialization checks in the fast path.\n// We allocate a user TLS slot at process initialization (see `windows/prim.c`)\n// and store the offset `_mi_win_tls_offset`.\n#define MI_HAS_TLS_SLOT  1              // 2 = we can reliably initialize the slot (saving a test on each malloc)\n\nextern mi_decl_hidden size_t _mi_win_tls_offset;\n\n#if MI_WIN_USE_FIXED_TLS > 1\n#define MI_TLS_SLOT     (MI_WIN_USE_FIXED_TLS)\n#elif MI_SIZE_SIZE == 4\n#define MI_TLS_SLOT     (0x0E10 + _mi_win_tls_offset)  // User TLS slots <https://en.wikipedia.org/wiki/Win32_Thread_Information_Block>\n#else\n#define MI_TLS_SLOT     (0x1480 + _mi_win_tls_offset)  // User TLS slots <https://en.wikipedia.org/wiki/Win32_Thread_Information_Block>\n#endif\n\nstatic inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {\n  #if (_M_X64 || _M_AMD64) && !defined(_M_ARM64EC)\n  return (void*)__readgsqword((unsigned long)slot);   // direct load at offset from gs\n  #elif _M_IX86 && !defined(_M_ARM64EC)\n  return (void*)__readfsdword((unsigned long)slot);   // direct load at offset from fs\n  #else\n  return ((void**)NtCurrentTeb())[slot / sizeof(void*)];\n  #endif\n}\nstatic inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexcept {\n  ((void**)NtCurrentTeb())[slot / sizeof(void*)] = value;\n}\n\n#endif\n\n\n\n//-------------------------------------------------------------------\n// Get a fast unique thread id.\n//\n// Getting the thread id should be performant as it is called in the\n// fast path of `_mi_free` and we specialize for various platforms as\n// inlined definitions. Regular code should call `init.c:_mi_thread_id()`.\n// We only require _mi_prim_thread_id() to return a unique id\n// for each thread (unequal to zero).\n//-------------------------------------------------------------------\n\n\n// Do we have __builtin_thread_pointer? This would be the preferred way to get a unique thread id\n// but unfortunately, it seems we cannot test for this reliably at this time (see issue #883)\n// Nevertheless, it seems needed on older graviton platforms (see issue #851).\n// For now, we only enable this for specific platforms.\n#if !defined(__APPLE__)  /* on apple (M1) the wrong register is read (tpidr_el0 instead of tpidrro_el0) so fall back to TLS slot assembly (<https://github.com/microsoft/mimalloc/issues/343#issuecomment-763272369>)*/ \\\n    && !defined(__CYGWIN__) \\\n    && !defined(MI_LIBC_MUSL) \\\n    && (!defined(__clang_major__) || __clang_major__ >= 14)  /* older clang versions emit bad code; fall back to using the TLS slot (<https://lore.kernel.org/linux-arm-kernel/202110280952.352F66D8@keescook/T/>) */\n  #if    (defined(__GNUC__) && (__GNUC__ >= 7)  && defined(__aarch64__)) /* aarch64 for older gcc versions (issue #851) */ \\\n      || (defined(__GNUC__) && (__GNUC__ >= 11) && defined(__x86_64__)) \\\n      || (defined(__clang_major__) && (__clang_major__ >= 14) && (defined(__aarch64__) || defined(__x86_64__)))\n    #define MI_USE_BUILTIN_THREAD_POINTER  1\n  #endif\n#endif\n\n\n\n// defined in `init.c`; do not use these directly\nextern mi_decl_hidden mi_decl_thread mi_heap_t* _mi_heap_default;  // default heap to allocate from\nextern mi_decl_hidden bool _mi_process_is_initialized;             // has mi_process_init been called?\n\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept;\n\n// Get a unique id for the current thread.\n#if defined(MI_PRIM_THREAD_ID)\n\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {\n  return MI_PRIM_THREAD_ID();  // used for example by CPython for a free threaded build (see python/cpython#115488)\n}\n\n#elif defined(_WIN32)\n\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {\n  // Windows: works on Intel and ARM in both 32- and 64-bit\n  return (uintptr_t)NtCurrentTeb();\n}\n\n#elif MI_USE_BUILTIN_THREAD_POINTER\n\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {\n  // Works on most Unix based platforms with recent compilers\n  return (uintptr_t)__builtin_thread_pointer();\n}\n\n#elif MI_HAS_TLS_SLOT\n\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {\n  #if defined(__BIONIC__)\n    // issue #384, #495: on the Bionic libc (Android), slot 1 is the thread id\n    // see: https://github.com/aosp-mirror/platform_bionic/blob/c44b1d0676ded732df4b3b21c5f798eacae93228/libc/platform/bionic/tls_defines.h#L86\n    return (uintptr_t)mi_prim_tls_slot(1);\n  #else\n    // in all our other targets, slot 0 is the thread id\n    // glibc: https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/x86_64/nptl/tls.h\n    // apple: https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L36\n    return (uintptr_t)mi_prim_tls_slot(0);\n  #endif\n}\n\n#else\n\n// otherwise use portable C, taking the address of a thread local variable (this is still very fast on most platforms).\nstatic inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {\n  return (uintptr_t)&_mi_heap_default;\n}\n\n#endif\n\n\n\n/* ----------------------------------------------------------------------------------------\nGet the thread local default heap: `_mi_prim_get_default_heap()`\n\nThis is inlined here as it is on the fast path for allocation functions.\n\nOn most platforms (Windows, Linux, FreeBSD, NetBSD, etc), this just returns a\n__thread local variable (`_mi_heap_default`). With the initial-exec TLS model this ensures\nthat the storage will always be available (allocated on the thread stacks).\n\nOn some platforms though we cannot use that when overriding `malloc` since the underlying\nTLS implementation (or the loader) will call itself `malloc` on a first access and recurse.\nWe try to circumvent this in an efficient way:\n- macOSX : we use an unused TLS slot from the OS allocated slots (MI_TLS_SLOT). On OSX, the\n           loader itself calls `malloc` even before the modules are initialized.\n- OpenBSD: we use an unused slot from the pthread block (MI_TLS_PTHREAD_SLOT_OFS).\n- DragonFly: defaults are working but seem slow compared to freeBSD (see PR #323)\n------------------------------------------------------------------------------------------- */\n\nstatic inline mi_heap_t* mi_prim_get_default_heap(void);\n\n#if defined(MI_MALLOC_OVERRIDE)\n#if defined(__APPLE__) // macOS\n  #define MI_TLS_SLOT               89  // seems unused?\n  // other possible unused ones are 9, 29, __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4 (94), __PTK_FRAMEWORK_GC_KEY9 (112) and __PTK_FRAMEWORK_OLDGC_KEY9 (89)\n  // see <https://github.com/rweichler/substrate/blob/master/include/pthread_machdep.h>\n#elif defined(__OpenBSD__)\n  // use end bytes of a name; goes wrong if anyone uses names > 23 characters (ptrhread specifies 16)\n  // see <https://github.com/openbsd/src/blob/master/lib/libc/include/thread_private.h#L371>\n  #define MI_TLS_PTHREAD_SLOT_OFS   (6*sizeof(int) + 4*sizeof(void*) + 24)\n  // #elif defined(__DragonFly__)\n  // #warning \"mimalloc is not working correctly on DragonFly yet.\"\n  // #define MI_TLS_PTHREAD_SLOT_OFS   (4 + 1*sizeof(void*))  // offset `uniqueid` (also used by gdb?) <https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/lib/libthread_xu/thread/thr_private.h#L458>\n#elif defined(__ANDROID__)\n  // See issue #381\n  #define MI_TLS_PTHREAD\n#endif\n#endif\n\n\n#if MI_TLS_SLOT\n# if !defined(MI_HAS_TLS_SLOT)\n#  error \"trying to use a TLS slot for the default heap, but the mi_prim_tls_slot primitives are not defined\"\n# endif\n\nstatic inline mi_heap_t* mi_prim_get_default_heap(void) {\n  mi_heap_t* heap = (mi_heap_t*)mi_prim_tls_slot(MI_TLS_SLOT);\n  #if MI_HAS_TLS_SLOT == 1   // check if the TLS slot is initialized\n  if mi_unlikely(heap == NULL) {\n    #ifdef __GNUC__\n    __asm(\"\"); // prevent conditional load of the address of _mi_heap_empty\n    #endif\n    heap = (mi_heap_t*)&_mi_heap_empty;\n  }\n  #endif\n  return heap;\n}\n\n#elif defined(MI_TLS_PTHREAD_SLOT_OFS)\n\nstatic inline mi_heap_t** mi_prim_tls_pthread_heap_slot(void) {\n  pthread_t self = pthread_self();\n  #if defined(__DragonFly__)\n  if (self==NULL) return NULL;\n  #endif\n  return (mi_heap_t**)((uint8_t*)self + MI_TLS_PTHREAD_SLOT_OFS);\n}\n\nstatic inline mi_heap_t* mi_prim_get_default_heap(void) {\n  mi_heap_t** pheap = mi_prim_tls_pthread_heap_slot();\n  if mi_unlikely(pheap == NULL) return _mi_heap_main_get();\n  mi_heap_t* heap = *pheap;\n  if mi_unlikely(heap == NULL) return (mi_heap_t*)&_mi_heap_empty;\n  return heap;\n}\n\n#elif defined(MI_TLS_PTHREAD)\n\nextern mi_decl_hidden pthread_key_t _mi_heap_default_key;\nstatic inline mi_heap_t* mi_prim_get_default_heap(void) {\n  mi_heap_t* heap = (mi_unlikely(_mi_heap_default_key == (pthread_key_t)(-1)) ? _mi_heap_main_get() : (mi_heap_t*)pthread_getspecific(_mi_heap_default_key));\n  return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap);\n}\n\n#else // default using a thread local variable; used on most platforms.\n\nstatic inline mi_heap_t* mi_prim_get_default_heap(void) {\n  #if defined(MI_TLS_RECURSE_GUARD)\n  if (mi_unlikely(!_mi_process_is_initialized)) return _mi_heap_main_get();\n  #endif\n  return _mi_heap_default;\n}\n\n#endif  // mi_prim_get_default_heap()\n\n\n#endif  // MIMALLOC_PRIM_H\n"
  },
  {
    "path": "include/mimalloc/track.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_TRACK_H\n#define MIMALLOC_TRACK_H\n\n/* ------------------------------------------------------------------------------------------------------\nTrack memory ranges with macros for tools like Valgrind address sanitizer, or other memory checkers.\nThese can be defined for tracking allocation:\n\n  #define mi_track_malloc_size(p,reqsize,size,zero)\n  #define mi_track_free_size(p,_size)\n\nThe macros are set up such that the size passed to `mi_track_free_size`\nalways matches the size of `mi_track_malloc_size`. (currently, `size == mi_usable_size(p)`).\nThe `reqsize` is what the user requested, and `size >= reqsize`.\nThe `size` is either byte precise (and `size==reqsize`) if `MI_PADDING` is enabled,\nor otherwise it is the usable block size which may be larger than the original request.\nUse `_mi_block_size_of(void* p)` to get the full block size that was allocated (including padding etc).\nThe `zero` parameter is `true` if the allocated block is zero initialized.\n\nOptional:\n\n  #define mi_track_align(p,alignedp,offset,size)\n  #define mi_track_resize(p,oldsize,newsize)\n  #define mi_track_init()\n\nThe `mi_track_align` is called right after a `mi_track_malloc` for aligned pointers in a block.\nThe corresponding `mi_track_free` still uses the block start pointer and original size (corresponding to the `mi_track_malloc`).\nThe `mi_track_resize` is currently unused but could be called on reallocations within a block.\n`mi_track_init` is called at program start.\n\nThe following macros are for tools like asan and valgrind to track whether memory is\ndefined, undefined, or not accessible at all:\n\n  #define mi_track_mem_defined(p,size)\n  #define mi_track_mem_undefined(p,size)\n  #define mi_track_mem_noaccess(p,size)\n\n-------------------------------------------------------------------------------------------------------*/\n\n#if MI_TRACK_VALGRIND\n// valgrind tool\n\n#define MI_TRACK_ENABLED      1\n#define MI_TRACK_HEAP_DESTROY 1           // track free of individual blocks on heap_destroy\n#define MI_TRACK_TOOL         \"valgrind\"\n\n#include <valgrind/valgrind.h>\n#include <valgrind/memcheck.h>\n\n#define mi_track_malloc_size(p,reqsize,size,zero) VALGRIND_MALLOCLIKE_BLOCK(p,size,MI_PADDING_SIZE /*red zone*/,zero)\n#define mi_track_free_size(p,_size)               VALGRIND_FREELIKE_BLOCK(p,MI_PADDING_SIZE /*red zone*/)\n#define mi_track_resize(p,oldsize,newsize)        VALGRIND_RESIZEINPLACE_BLOCK(p,oldsize,newsize,MI_PADDING_SIZE /*red zone*/)\n#define mi_track_mem_defined(p,size)              VALGRIND_MAKE_MEM_DEFINED(p,size)\n#define mi_track_mem_undefined(p,size)            VALGRIND_MAKE_MEM_UNDEFINED(p,size)\n#define mi_track_mem_noaccess(p,size)             VALGRIND_MAKE_MEM_NOACCESS(p,size)\n\n#elif MI_TRACK_ASAN\n// address sanitizer\n\n#define MI_TRACK_ENABLED      1\n#define MI_TRACK_HEAP_DESTROY 0\n#define MI_TRACK_TOOL         \"asan\"\n\n#include <sanitizer/asan_interface.h>\n\n#define mi_track_malloc_size(p,reqsize,size,zero) ASAN_UNPOISON_MEMORY_REGION(p,size)\n#define mi_track_free_size(p,size)                ASAN_POISON_MEMORY_REGION(p,size)\n#define mi_track_mem_defined(p,size)              ASAN_UNPOISON_MEMORY_REGION(p,size)\n#define mi_track_mem_undefined(p,size)            ASAN_UNPOISON_MEMORY_REGION(p,size)\n#define mi_track_mem_noaccess(p,size)             ASAN_POISON_MEMORY_REGION(p,size)\n\n#elif MI_TRACK_ETW\n// windows event tracing\n\n#define MI_TRACK_ENABLED      1\n#define MI_TRACK_HEAP_DESTROY 1\n#define MI_TRACK_TOOL         \"ETW\"\n\n#include \"../src/prim/windows/etw.h\"\n\n#define mi_track_init()                           EventRegistermicrosoft_windows_mimalloc();\n#define mi_track_malloc_size(p,reqsize,size,zero) EventWriteETW_MI_ALLOC((UINT64)(p), size)\n#define mi_track_free_size(p,size)                EventWriteETW_MI_FREE((UINT64)(p), size)\n\n#else\n// no tracking\n\n#define MI_TRACK_ENABLED      0\n#define MI_TRACK_HEAP_DESTROY 0\n#define MI_TRACK_TOOL         \"none\"\n\n#define mi_track_malloc_size(p,reqsize,size,zero)\n#define mi_track_free_size(p,_size)\n\n#endif\n\n// -------------------\n// Utility definitions\n\n#ifndef mi_track_resize\n#define mi_track_resize(p,oldsize,newsize)      mi_track_free_size(p,oldsize); mi_track_malloc(p,newsize,false)\n#endif\n\n#ifndef mi_track_align\n#define mi_track_align(p,alignedp,offset,size)  mi_track_mem_noaccess(p,offset)\n#endif\n\n#ifndef mi_track_init\n#define mi_track_init()\n#endif\n\n#ifndef mi_track_mem_defined\n#define mi_track_mem_defined(p,size)\n#endif\n\n#ifndef mi_track_mem_undefined\n#define mi_track_mem_undefined(p,size)\n#endif\n\n#ifndef mi_track_mem_noaccess\n#define mi_track_mem_noaccess(p,size)\n#endif\n\n\n#if MI_PADDING\n#define mi_track_malloc(p,reqsize,zero) \\\n  if ((p)!=NULL) { \\\n    mi_assert_internal(mi_usable_size(p)==(reqsize)); \\\n    mi_track_malloc_size(p,reqsize,reqsize,zero); \\\n  }\n#else\n#define mi_track_malloc(p,reqsize,zero) \\\n  if ((p)!=NULL) { \\\n    mi_assert_internal(mi_usable_size(p)>=(reqsize)); \\\n    mi_track_malloc_size(p,reqsize,mi_usable_size(p),zero); \\\n  }\n#endif\n\n#endif\n"
  },
  {
    "path": "include/mimalloc/types.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_TYPES_H\n#define MIMALLOC_TYPES_H\n\n// --------------------------------------------------------------------------\n// This file contains the main type definitions for mimalloc:\n// mi_heap_t      : all data for a thread-local heap, contains\n//                  lists of all managed heap pages.\n// mi_segment_t   : a larger chunk of memory (32MiB on 64-bit) from where pages\n//                  are allocated. A segment is divided in slices (64KiB) from\n//                  which pages are allocated.\n// mi_page_t      : a \"mimalloc\" page (usually 64KiB or 512KiB) from\n//                  where objects are allocated.\n//                  Note: we write \"OS page\" for OS memory pages while\n//                  using plain \"page\" for mimalloc pages (`mi_page_t`).\n// --------------------------------------------------------------------------\n\n\n#include <mimalloc-stats.h>\n#include <stddef.h>   // ptrdiff_t\n#include <stdint.h>   // uintptr_t, uint16_t, etc\n#include <stdbool.h>  // bool\n#include \"atomic.h\"   // _Atomic\n\n#ifdef _MSC_VER\n#pragma warning(disable:4214) // bitfield is not int\n#endif\n\n// Minimal alignment necessary. On most platforms 16 bytes are needed\n// due to SSE registers for example. This must be at least `sizeof(void*)`\n#ifndef MI_MAX_ALIGN_SIZE\n#define MI_MAX_ALIGN_SIZE  16   // sizeof(max_align_t)\n#endif\n\n// ------------------------------------------------------\n// Variants\n// ------------------------------------------------------\n\n// Define NDEBUG in the release version to disable assertions.\n// #define NDEBUG\n\n// Define MI_TRACK_<tool> to enable tracking support\n// #define MI_TRACK_VALGRIND 1\n// #define MI_TRACK_ASAN     1\n// #define MI_TRACK_ETW      1\n\n// Define MI_STAT as 1 to maintain statistics; set it to 2 to have detailed statistics (but costs some performance).\n// #define MI_STAT 1\n\n// Define MI_SECURE to enable security mitigations\n// #define MI_SECURE 1  // guard page around metadata\n// #define MI_SECURE 2  // guard page around each mimalloc page\n// #define MI_SECURE 3  // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free)\n// #define MI_SECURE 4  // checks for double free. (may be more expensive)\n\n#if !defined(MI_SECURE)\n#define MI_SECURE 0\n#endif\n\n// Define MI_DEBUG for assertion and invariant checking\n// #define MI_DEBUG 1  // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free. (cmake -DMI_DEBUG=ON)\n// #define MI_DEBUG 2  // + internal assertion checks (cmake -DMI_DEBUG_INTERNAL=ON)\n// #define MI_DEBUG 3  // + extensive internal invariant checking (cmake -DMI_DEBUG_FULL=ON)\n#if !defined(MI_DEBUG)\n#if defined(MI_BUILD_RELEASE) || defined(NDEBUG)\n#define MI_DEBUG 0\n#else\n#define MI_DEBUG 2\n#endif\n#endif\n\n// Use guard pages behind objects of a certain size (set by the MIMALLOC_DEBUG_GUARDED_MIN/MAX options)\n// Padding should be disabled when using guard pages\n// #define MI_GUARDED 1\n#if defined(MI_GUARDED)\n#define MI_PADDING  0\n#endif\n\n// Reserve extra padding at the end of each block to be more resilient against heap block overflows.\n// The padding can detect buffer overflow on free.\n#if !defined(MI_PADDING) && (MI_SECURE>=3 || MI_DEBUG>=1 || (MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_TRACK_ETW))\n#define MI_PADDING  1\n#endif\n\n// Check padding bytes; allows byte-precise buffer overflow detection\n#if !defined(MI_PADDING_CHECK) && MI_PADDING && (MI_SECURE>=3 || MI_DEBUG>=1)\n#define MI_PADDING_CHECK 1\n#endif\n\n\n// Encoded free lists allow detection of corrupted free lists\n// and can detect buffer overflows, modify after free, and double `free`s.\n#if (MI_SECURE>=3 || MI_DEBUG>=1)\n#define MI_ENCODE_FREELIST  1\n#endif\n\n\n// We used to abandon huge pages in order to eagerly deallocate it if freed from another thread.\n// Unfortunately, that makes it not possible to visit them during a heap walk or include them in a\n// `mi_heap_destroy`. We therefore instead reset/decommit the huge blocks nowadays if freed from\n// another thread so the memory becomes \"virtually\" available (and eventually gets properly freed by\n// the owning thread).\n// #define MI_HUGE_PAGE_ABANDON 1\n\n\n// ------------------------------------------------------\n// Platform specific values\n// ------------------------------------------------------\n\n// ------------------------------------------------------\n// Size of a pointer.\n// We assume that `sizeof(void*)==sizeof(intptr_t)`\n// and it holds for all platforms we know of.\n//\n// However, the C standard only requires that:\n//  p == (void*)((intptr_t)p))\n// but we also need:\n//  i == (intptr_t)((void*)i)\n// or otherwise one might define an intptr_t type that is larger than a pointer...\n// ------------------------------------------------------\n\n#if INTPTR_MAX > INT64_MAX\n# define MI_INTPTR_SHIFT (4)  // assume 128-bit  (as on arm CHERI for example)\n#elif INTPTR_MAX == INT64_MAX\n# define MI_INTPTR_SHIFT (3)\n#elif INTPTR_MAX == INT32_MAX\n# define MI_INTPTR_SHIFT (2)\n#else\n#error platform pointers must be 32, 64, or 128 bits\n#endif\n\n#if SIZE_MAX == UINT64_MAX\n# define MI_SIZE_SHIFT (3)\ntypedef int64_t  mi_ssize_t;\n#elif SIZE_MAX == UINT32_MAX\n# define MI_SIZE_SHIFT (2)\ntypedef int32_t  mi_ssize_t;\n#else\n#error platform objects must be 32 or 64 bits\n#endif\n\n#if (SIZE_MAX/2) > LONG_MAX\n# define MI_ZU(x)  x##ULL\n# define MI_ZI(x)  x##LL\n#else\n# define MI_ZU(x)  x##UL\n# define MI_ZI(x)  x##L\n#endif\n\n#define MI_INTPTR_SIZE  (1<<MI_INTPTR_SHIFT)\n#define MI_INTPTR_BITS  (MI_INTPTR_SIZE*8)\n\n#define MI_SIZE_SIZE  (1<<MI_SIZE_SHIFT)\n#define MI_SIZE_BITS  (MI_SIZE_SIZE*8)\n\n#define MI_KiB     (MI_ZU(1024))\n#define MI_MiB     (MI_KiB*MI_KiB)\n#define MI_GiB     (MI_MiB*MI_KiB)\n\n\n// ------------------------------------------------------\n// Main internal data-structures\n// ------------------------------------------------------\n\n// Main tuning parameters for segment and page sizes\n// Sizes for 64-bit (usually divide by two for 32-bit)\n#ifndef MI_SEGMENT_SLICE_SHIFT\n#define MI_SEGMENT_SLICE_SHIFT            (13 + MI_INTPTR_SHIFT)         // 64KiB  (32KiB on 32-bit)\n#endif\n\n#ifndef MI_SEGMENT_SHIFT\n#if MI_INTPTR_SIZE > 4\n#define MI_SEGMENT_SHIFT                  ( 9 + MI_SEGMENT_SLICE_SHIFT)  // 32MiB\n#else\n#define MI_SEGMENT_SHIFT                  ( 7 + MI_SEGMENT_SLICE_SHIFT)  // 4MiB on 32-bit\n#endif\n#endif\n\n#ifndef MI_SMALL_PAGE_SHIFT\n#define MI_SMALL_PAGE_SHIFT               (MI_SEGMENT_SLICE_SHIFT)       // 64KiB\n#endif\n#ifndef MI_MEDIUM_PAGE_SHIFT\n#define MI_MEDIUM_PAGE_SHIFT              ( 3 + MI_SMALL_PAGE_SHIFT)     // 512KiB\n#endif\n\n// Derived constants\n#define MI_SEGMENT_SIZE                   (MI_ZU(1)<<MI_SEGMENT_SHIFT)\n#define MI_SEGMENT_ALIGN                  MI_SEGMENT_SIZE\n#define MI_SEGMENT_MASK                   ((uintptr_t)(MI_SEGMENT_ALIGN - 1))\n#define MI_SEGMENT_SLICE_SIZE             (MI_ZU(1)<< MI_SEGMENT_SLICE_SHIFT)\n#define MI_SLICES_PER_SEGMENT             (MI_SEGMENT_SIZE / MI_SEGMENT_SLICE_SIZE) // 512 (128 on 32-bit)\n\n#define MI_SMALL_PAGE_SIZE                (MI_ZU(1)<<MI_SMALL_PAGE_SHIFT)\n#define MI_MEDIUM_PAGE_SIZE               (MI_ZU(1)<<MI_MEDIUM_PAGE_SHIFT)\n\n#define MI_SMALL_OBJ_SIZE_MAX             (MI_SMALL_PAGE_SIZE/8)   // 8 KiB on 64-bit\n#define MI_MEDIUM_OBJ_SIZE_MAX            (MI_MEDIUM_PAGE_SIZE/8)  // 64 KiB on 64-bit\n#define MI_MEDIUM_OBJ_WSIZE_MAX           (MI_MEDIUM_OBJ_SIZE_MAX/MI_INTPTR_SIZE)\n#define MI_LARGE_OBJ_SIZE_MAX             (MI_SEGMENT_SIZE/2)      // 16 MiB on 64-bit\n#define MI_LARGE_OBJ_WSIZE_MAX            (MI_LARGE_OBJ_SIZE_MAX/MI_INTPTR_SIZE)\n\n// Maximum number of size classes. (spaced exponentially in 12.5% increments)\n#if MI_BIN_HUGE != 73U\n#error \"mimalloc internal: expecting 73 bins\"\n#endif\n\n#if (MI_MEDIUM_OBJ_WSIZE_MAX >= 655360)\n#error \"mimalloc internal: define more bins\"\n#endif\n\n// Maximum block size for which blocks are guaranteed to be block size aligned. (see `segment.c:_mi_segment_page_start`)\n#define MI_MAX_ALIGN_GUARANTEE            (MI_MEDIUM_OBJ_SIZE_MAX)\n\n// Alignments over MI_BLOCK_ALIGNMENT_MAX are allocated in dedicated huge page segments\n#define MI_BLOCK_ALIGNMENT_MAX            (MI_SEGMENT_SIZE >> 1)\n\n// Maximum slice count (255) for which we can find the page for interior pointers\n#define MI_MAX_SLICE_OFFSET_COUNT         ((MI_BLOCK_ALIGNMENT_MAX / MI_SEGMENT_SLICE_SIZE) - 1)\n\n// we never allocate more than PTRDIFF_MAX (see also <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)\n// on 64-bit+ systems we also limit the maximum allocation size such that the slice count fits in 32-bits. (issue #877)\n#if (PTRDIFF_MAX > INT32_MAX) && (PTRDIFF_MAX >= (MI_SEGMENT_SLIZE_SIZE * UINT32_MAX))\n#define MI_MAX_ALLOC_SIZE   (MI_SEGMENT_SLICE_SIZE * (UINT32_MAX-1))\n#else\n#define MI_MAX_ALLOC_SIZE   PTRDIFF_MAX\n#endif\n\n\n// ------------------------------------------------------\n// Mimalloc pages contain allocated blocks\n// ------------------------------------------------------\n\n// The free lists use encoded next fields\n// (Only actually encodes when MI_ENCODED_FREELIST is defined.)\ntypedef uintptr_t  mi_encoded_t;\n\n// thread id's\ntypedef size_t     mi_threadid_t;\n\n// free lists contain blocks\ntypedef struct mi_block_s {\n  mi_encoded_t next;\n} mi_block_t;\n\n#if MI_GUARDED\n// we always align guarded pointers in a block at an offset\n// the block `next` field is then used as a tag to distinguish regular offset aligned blocks from guarded ones\n#define MI_BLOCK_TAG_ALIGNED   ((mi_encoded_t)(0))\n#define MI_BLOCK_TAG_GUARDED   (~MI_BLOCK_TAG_ALIGNED)\n#endif\n\n\n// The delayed flags are used for efficient multi-threaded free-ing\ntypedef enum mi_delayed_e {\n  MI_USE_DELAYED_FREE   = 0, // push on the owning heap thread delayed list\n  MI_DELAYED_FREEING    = 1, // temporary: another thread is accessing the owning heap\n  MI_NO_DELAYED_FREE    = 2, // optimize: push on page local thread free queue if another block is already in the heap thread delayed free list\n  MI_NEVER_DELAYED_FREE = 3  // sticky: used for abandoned pages without a owning heap; this only resets on page reclaim\n} mi_delayed_t;\n\n\n// The `in_full` and `has_aligned` page flags are put in a union to efficiently\n// test if both are false (`full_aligned == 0`) in the `mi_free` routine.\n#if !MI_TSAN\ntypedef union mi_page_flags_s {\n  uint8_t full_aligned;\n  struct {\n    uint8_t in_full : 1;\n    uint8_t has_aligned : 1;\n  } x;\n} mi_page_flags_t;\n#else\n// under thread sanitizer, use a byte for each flag to suppress warning, issue #130\ntypedef union mi_page_flags_s {\n  uint32_t full_aligned;\n  struct {\n    uint8_t in_full;\n    uint8_t has_aligned;\n  } x;\n} mi_page_flags_t;\n#endif\n\n// Thread free list.\n// We use the bottom 2 bits of the pointer for mi_delayed_t flags\ntypedef uintptr_t mi_thread_free_t;\n\n// A page contains blocks of one specific size (`block_size`).\n// Each page has three list of free blocks:\n// `free` for blocks that can be allocated,\n// `local_free` for freed blocks that are not yet available to `mi_malloc`\n// `thread_free` for freed blocks by other threads\n// The `local_free` and `thread_free` lists are migrated to the `free` list\n// when it is exhausted. The separate `local_free` list is necessary to\n// implement a monotonic heartbeat. The `thread_free` list is needed for\n// avoiding atomic operations in the common case.\n//\n// `used - |thread_free|` == actual blocks that are in use (alive)\n// `used - |thread_free| + |free| + |local_free| == capacity`\n//\n// We don't count `freed` (as |free|) but use `used` to reduce\n// the number of memory accesses in the `mi_page_all_free` function(s).\n//\n// Notes:\n// - Access is optimized for `free.c:mi_free` and `alloc.c:mi_page_alloc`\n// - Using `uint16_t` does not seem to slow things down\n// - The size is 12 words on 64-bit which helps the page index calculations\n//   (and 14 words on 32-bit, and encoded free lists add 2 words)\n// - `xthread_free` uses the bottom bits as a delayed-free flags to optimize\n//   concurrent frees where only the first concurrent free adds to the owning\n//   heap `thread_delayed_free` list (see `free.c:mi_free_block_mt`).\n//   The invariant is that no-delayed-free is only set if there is\n//   at least one block that will be added, or as already been added, to\n//   the owning heap `thread_delayed_free` list. This guarantees that pages\n//   will be freed correctly even if only other threads free blocks.\ntypedef struct mi_page_s {\n  // \"owned\" by the segment\n  uint32_t              slice_count;       // slices in this page (0 if not a page)\n  uint32_t              slice_offset;      // distance from the actual page data slice (0 if a page)\n  uint8_t               is_committed:1;    // `true` if the page virtual memory is committed\n  uint8_t               is_zero_init:1;    // `true` if the page was initially zero initialized\n  uint8_t               is_huge:1;         // `true` if the page is in a huge segment (`segment->kind == MI_SEGMENT_HUGE`)\n                                           // padding\n  // layout like this to optimize access in `mi_malloc` and `mi_free`\n  uint16_t              capacity;          // number of blocks committed, must be the first field, see `segment.c:page_clear`\n  uint16_t              reserved;          // number of blocks reserved in memory\n  mi_page_flags_t       flags;             // `in_full` and `has_aligned` flags (8 bits)\n  uint8_t               free_is_zero:1;    // `true` if the blocks in the free list are zero initialized\n  uint8_t               retire_expire:7;   // expiration count for retired blocks\n\n  mi_block_t*           free;              // list of available free blocks (`malloc` allocates from this list)\n  mi_block_t*           local_free;        // list of deferred free blocks by this thread (migrates to `free`)\n  uint16_t              used;              // number of blocks in use (including blocks in `thread_free`)\n  uint8_t               block_size_shift;  // if not zero, then `(1 << block_size_shift) == block_size` (only used for fast path in `free.c:_mi_page_ptr_unalign`)\n  uint8_t               heap_tag;          // tag of the owning heap, used to separate heaps by object type\n                                           // padding\n  size_t                block_size;        // size available in each block (always `>0`)\n  uint8_t*              page_start;        // start of the page area containing the blocks\n\n  #if (MI_ENCODE_FREELIST || MI_PADDING)\n  uintptr_t             keys[2];           // two random keys to encode the free lists (see `_mi_block_next`) or padding canary\n  #endif\n\n  _Atomic(mi_thread_free_t) xthread_free;  // list of deferred free blocks freed by other threads\n  _Atomic(uintptr_t)        xheap;\n\n  struct mi_page_s*     next;              // next page owned by this thread with the same `block_size`\n  struct mi_page_s*     prev;              // previous page owned by this thread with the same `block_size`\n\n  // 64-bit 11 words, 32-bit 13 words, (+2 for secure)\n  void* padding[1];\n} mi_page_t;\n\n\n\n// ------------------------------------------------------\n// Mimalloc segments contain mimalloc pages\n// ------------------------------------------------------\n\ntypedef enum mi_page_kind_e {\n  MI_PAGE_SMALL,    // small blocks go into 64KiB pages inside a segment\n  MI_PAGE_MEDIUM,   // medium blocks go into 512KiB pages inside a segment\n  MI_PAGE_LARGE,    // larger blocks go into a single page spanning a whole segment\n  MI_PAGE_HUGE      // a huge page is a single page in a segment of variable size\n                    // used for blocks `> MI_LARGE_OBJ_SIZE_MAX` or an aligment `> MI_BLOCK_ALIGNMENT_MAX`.\n} mi_page_kind_t;\n\ntypedef enum mi_segment_kind_e {\n  MI_SEGMENT_NORMAL, // MI_SEGMENT_SIZE size with pages inside.\n  MI_SEGMENT_HUGE,   // segment with just one huge page inside.\n} mi_segment_kind_t;\n\n// ------------------------------------------------------\n// A segment holds a commit mask where a bit is set if\n// the corresponding MI_COMMIT_SIZE area is committed.\n// The MI_COMMIT_SIZE must be a multiple of the slice\n// size. If it is equal we have the most fine grained\n// decommit (but setting it higher can be more efficient).\n// The MI_MINIMAL_COMMIT_SIZE is the minimal amount that will\n// be committed in one go which can be set higher than\n// MI_COMMIT_SIZE for efficiency (while the decommit mask\n// is still tracked in fine-grained MI_COMMIT_SIZE chunks)\n// ------------------------------------------------------\n\n#define MI_MINIMAL_COMMIT_SIZE      (1*MI_SEGMENT_SLICE_SIZE)\n#define MI_COMMIT_SIZE              (MI_SEGMENT_SLICE_SIZE)              // 64KiB\n#define MI_COMMIT_MASK_BITS         (MI_SEGMENT_SIZE / MI_COMMIT_SIZE)\n#define MI_COMMIT_MASK_FIELD_BITS    MI_SIZE_BITS\n#define MI_COMMIT_MASK_FIELD_COUNT  (MI_COMMIT_MASK_BITS / MI_COMMIT_MASK_FIELD_BITS)\n\n#if (MI_COMMIT_MASK_BITS != (MI_COMMIT_MASK_FIELD_COUNT * MI_COMMIT_MASK_FIELD_BITS))\n#error \"the segment size must be exactly divisible by the (commit size * size_t bits)\"\n#endif\n\ntypedef struct mi_commit_mask_s {\n  size_t mask[MI_COMMIT_MASK_FIELD_COUNT];\n} mi_commit_mask_t;\n\ntypedef mi_page_t  mi_slice_t;\ntypedef int64_t    mi_msecs_t;\n\n\n// ---------------------------------------------------------------\n// a memory id tracks the provenance of arena/OS allocated memory\n// ---------------------------------------------------------------\n\n// Memory can reside in arena's, direct OS allocated, or statically allocated. The memid keeps track of this.\ntypedef enum mi_memkind_e {\n  MI_MEM_NONE,      // not allocated\n  MI_MEM_EXTERNAL,  // not owned by mimalloc but provided externally (via `mi_manage_os_memory` for example)\n  MI_MEM_STATIC,    // allocated in a static area and should not be freed (for arena meta data for example)\n  MI_MEM_OS,        // allocated from the OS\n  MI_MEM_OS_HUGE,   // allocated as huge OS pages (usually 1GiB, pinned to physical memory)\n  MI_MEM_OS_REMAP,  // allocated in a remapable area (i.e. using `mremap`)\n  MI_MEM_ARENA      // allocated from an arena (the usual case)\n} mi_memkind_t;\n\nstatic inline bool mi_memkind_is_os(mi_memkind_t memkind) {\n  return (memkind >= MI_MEM_OS && memkind <= MI_MEM_OS_REMAP);\n}\n\ntypedef struct mi_memid_os_info {\n  void*         base;               // actual base address of the block (used for offset aligned allocations)\n  size_t        size;               // full allocation size\n} mi_memid_os_info_t;\n\ntypedef struct mi_memid_arena_info {\n  size_t        block_index;        // index in the arena\n  mi_arena_id_t id;                 // arena id (>= 1)\n  bool          is_exclusive;       // this arena can only be used for specific arena allocations\n} mi_memid_arena_info_t;\n\ntypedef struct mi_memid_s {\n  union {\n    mi_memid_os_info_t    os;       // only used for MI_MEM_OS\n    mi_memid_arena_info_t arena;    // only used for MI_MEM_ARENA\n  } mem;\n  bool          is_pinned;          // `true` if we cannot decommit/reset/protect in this memory (e.g. when allocated using large (2Mib) or huge (1GiB) OS pages)\n  bool          initially_committed;// `true` if the memory was originally allocated as committed\n  bool          initially_zero;     // `true` if the memory was originally zero initialized\n  mi_memkind_t  memkind;\n} mi_memid_t;\n\n\n// -----------------------------------------------------------------------------------------\n// Segments are large allocated memory blocks (32mb on 64 bit) from arenas or the OS.\n//\n// Inside segments we allocated fixed size mimalloc pages (`mi_page_t`) that contain blocks.\n// The start of a segment is this structure with a fixed number of slice entries (`slices`)\n// usually followed by a guard OS page and the actual allocation area with pages.\n// While a page is not allocated, we view it's data as a `mi_slice_t` (instead of a `mi_page_t`).\n// Of any free area, the first slice has the info and `slice_offset == 0`; for any subsequent\n// slices part of the area, the `slice_offset` is the byte offset back to the first slice\n// (so we can quickly find the page info on a free, `internal.h:_mi_segment_page_of`).\n// For slices, the `block_size` field is repurposed to signify if a slice is used (`1`) or not (`0`).\n// Small and medium pages use a fixed amount of slices to reduce slice fragmentation, while\n// large and huge pages span a variable amount of slices.\n\ntypedef struct mi_subproc_s mi_subproc_t;\n\ntypedef struct mi_segment_s {\n  // constant fields\n  mi_memid_t        memid;              // memory id for arena/OS allocation\n  bool              allow_decommit;     // can we decommmit the memory\n  bool              allow_purge;        // can we purge the memory (reset or decommit)\n  size_t            segment_size;\n  mi_subproc_t*     subproc;            // segment belongs to sub process\n\n  // segment fields\n  mi_msecs_t        purge_expire;       // purge slices in the `purge_mask` after this time\n  mi_commit_mask_t  purge_mask;         // slices that can be purged\n  mi_commit_mask_t  commit_mask;        // slices that are currently committed\n\n  // from here is zero initialized\n  struct mi_segment_s* next;            // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`)\n  bool              was_reclaimed;      // true if it was reclaimed (used to limit on-free reclamation)\n  bool              dont_free;          // can be temporarily true to ensure the segment is not freed\n  bool              free_is_zero;       // if free spans are zero\n\n  size_t            abandoned;          // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`)\n  size_t            abandoned_visits;   // count how often this segment is visited during abondoned reclamation (to force reclaim if it takes too long)\n  size_t            used;               // count of pages in use\n  uintptr_t         cookie;             // verify addresses in debug mode: `mi_ptr_cookie(segment) == segment->cookie`\n\n  struct mi_segment_s* abandoned_os_next; // only used for abandoned segments outside arena's, and only if `mi_option_visit_abandoned` is enabled\n  struct mi_segment_s* abandoned_os_prev;\n\n  size_t            segment_slices;      // for huge segments this may be different from `MI_SLICES_PER_SEGMENT`\n  size_t            segment_info_slices; // initial count of slices that we are using for segment info and possible guard pages.\n\n  // layout like this to optimize access in `mi_free`\n  mi_segment_kind_t kind;\n  size_t            slice_entries;       // entries in the `slices` array, at most `MI_SLICES_PER_SEGMENT`\n  _Atomic(mi_threadid_t) thread_id;      // unique id of the thread owning this segment\n\n  mi_slice_t        slices[MI_SLICES_PER_SEGMENT+1];  // one extra final entry for huge blocks with large alignment\n} mi_segment_t;\n\n\n// ------------------------------------------------------\n// Heaps\n// Provide first-class heaps to allocate from.\n// A heap just owns a set of pages for allocation and\n// can only be allocate/reallocate from the thread that created it.\n// Freeing blocks can be done from any thread though.\n// Per thread, the segments are shared among its heaps.\n// Per thread, there is always a default heap that is\n// used for allocation; it is initialized to statically\n// point to an empty heap to avoid initialization checks\n// in the fast path.\n// ------------------------------------------------------\n\n// Thread local data\ntypedef struct mi_tld_s mi_tld_t;\n\n// Pages of a certain block size are held in a queue.\ntypedef struct mi_page_queue_s {\n  mi_page_t* first;\n  mi_page_t* last;\n  size_t     block_size;\n} mi_page_queue_t;\n\n#define MI_BIN_FULL  (MI_BIN_HUGE+1)\n\n// Random context\ntypedef struct mi_random_cxt_s {\n  uint32_t input[16];\n  uint32_t output[16];\n  int      output_available;\n  bool     weak;\n} mi_random_ctx_t;\n\n\n// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows\n#if (MI_PADDING)\ntypedef struct mi_padding_s {\n  uint32_t canary; // encoded block value to check validity of the padding (in case of overflow)\n  uint32_t delta;  // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)\n} mi_padding_t;\n#define MI_PADDING_SIZE   (sizeof(mi_padding_t))\n#define MI_PADDING_WSIZE  ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE)\n#else\n#define MI_PADDING_SIZE   0\n#define MI_PADDING_WSIZE  0\n#endif\n\n#define MI_PAGES_DIRECT   (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1)\n\n\n// A heap owns a set of pages.\nstruct mi_heap_s {\n  mi_tld_t*             tld;\n  _Atomic(mi_block_t*)  thread_delayed_free;\n  mi_threadid_t         thread_id;                           // thread this heap belongs too\n  mi_arena_id_t         arena_id;                            // arena id if the heap belongs to a specific arena (or 0)\n  uintptr_t             cookie;                              // random cookie to verify pointers (see `_mi_ptr_cookie`)\n  uintptr_t             keys[2];                             // two random keys used to encode the `thread_delayed_free` list\n  mi_random_ctx_t       random;                              // random number context used for secure allocation\n  size_t                page_count;                          // total number of pages in the `pages` queues.\n  size_t                page_retired_min;                    // smallest retired index (retired pages are fully free, but still in the page queues)\n  size_t                page_retired_max;                    // largest retired index into the `pages` array.\n  long                  generic_count;                       // how often is `_mi_malloc_generic` called?\n  long                  generic_collect_count;               // how often is `_mi_malloc_generic` called without collecting?\n  mi_heap_t*            next;                                // list of heaps per thread\n  bool                  no_reclaim;                          // `true` if this heap should not reclaim abandoned pages\n  uint8_t               tag;                                 // custom tag, can be used for separating heaps based on the object types\n  #if MI_GUARDED\n  size_t                guarded_size_min;                    // minimal size for guarded objects\n  size_t                guarded_size_max;                    // maximal size for guarded objects\n  size_t                guarded_sample_rate;                 // sample rate (set to 0 to disable guarded pages)\n  size_t                guarded_sample_count;                // current sample count (counting down to 0)\n  #endif\n  mi_page_t*            pages_free_direct[MI_PAGES_DIRECT];  // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.\n  mi_page_queue_t       pages[MI_BIN_FULL + 1];              // queue of pages for each size class (or \"bin\")\n};\n\n\n// ------------------------------------------------------\n// Sub processes do not reclaim or visit segments\n// from other sub processes. These are essentially the\n// static variables of a process.\n// ------------------------------------------------------\n\nstruct mi_subproc_s {\n  _Atomic(size_t)    abandoned_count;         // count of abandoned segments for this sub-process\n  _Atomic(size_t)    abandoned_os_list_count; // count of abandoned segments in the os-list\n  mi_lock_t          abandoned_os_lock;       // lock for the abandoned os segment list (outside of arena's) (this lock protect list operations)\n  mi_lock_t          abandoned_os_visit_lock; // ensure only one thread per subproc visits the abandoned os list\n  mi_segment_t*      abandoned_os_list;       // doubly-linked list of abandoned segments outside of arena's (in OS allocated memory)\n  mi_segment_t*      abandoned_os_list_tail;  // the tail-end of the list\n  mi_memid_t         memid;                   // provenance of this memory block\n};\n\n\n// ------------------------------------------------------\n// Thread Local data\n// ------------------------------------------------------\n\n// A \"span\" is is an available range of slices. The span queues keep\n// track of slice spans of at most the given `slice_count` (but more than the previous size class).\ntypedef struct mi_span_queue_s {\n  mi_slice_t* first;\n  mi_slice_t* last;\n  size_t      slice_count;\n} mi_span_queue_t;\n\n#define MI_SEGMENT_BIN_MAX (35)     // 35 == mi_segment_bin(MI_SLICES_PER_SEGMENT)\n\n// Segments thread local data\ntypedef struct mi_segments_tld_s {\n  mi_span_queue_t     spans[MI_SEGMENT_BIN_MAX+1];  // free slice spans inside segments\n  size_t              count;        // current number of segments;\n  size_t              peak_count;   // peak number of segments\n  size_t              current_size; // current size of all segments\n  size_t              peak_size;    // peak size of all segments\n  size_t              reclaim_count;// number of reclaimed (abandoned) segments\n  mi_subproc_t*       subproc;      // sub-process this thread belongs to.\n  mi_stats_t*         stats;        // points to tld stats\n} mi_segments_tld_t;\n\n// Thread local data\nstruct mi_tld_s {\n  unsigned long long  heartbeat;     // monotonic heartbeat count\n  bool                recurse;       // true if deferred was called; used to prevent infinite recursion.\n  mi_heap_t*          heap_backing;  // backing heap of this thread (cannot be deleted)\n  mi_heap_t*          heaps;         // list of heaps in this thread (so we can abandon all when the thread terminates)\n  mi_segments_tld_t   segments;      // segment tld\n  mi_stats_t          stats;         // statistics\n};\n\n\n// ------------------------------------------------------\n// Debug\n// ------------------------------------------------------\n\n#if !defined(MI_DEBUG_UNINIT)\n#define MI_DEBUG_UNINIT     (0xD0)\n#endif\n#if !defined(MI_DEBUG_FREED)\n#define MI_DEBUG_FREED      (0xDF)\n#endif\n#if !defined(MI_DEBUG_PADDING)\n#define MI_DEBUG_PADDING    (0xDE)\n#endif\n\n\n// ------------------------------------------------------\n// Statistics\n// ------------------------------------------------------\n#ifndef MI_STAT\n#if (MI_DEBUG>0)\n#define MI_STAT 2\n#else\n#define MI_STAT 0\n#endif\n#endif\n\n// add to stat keeping track of the peak\nvoid _mi_stat_increase(mi_stat_count_t* stat, size_t amount);\nvoid _mi_stat_decrease(mi_stat_count_t* stat, size_t amount);\nvoid _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount);\n// counters can just be increased\nvoid _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);\n\n#if (MI_STAT)\n#define mi_stat_increase(stat,amount)         _mi_stat_increase( &(stat), amount)\n#define mi_stat_decrease(stat,amount)         _mi_stat_decrease( &(stat), amount)\n#define mi_stat_adjust_decrease(stat,amount)  _mi_stat_adjust_decrease( &(stat), amount)\n#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount)\n#else\n#define mi_stat_increase(stat,amount)         ((void)0)\n#define mi_stat_decrease(stat,amount)         ((void)0)\n#define mi_stat_adjust_decrease(stat,amount)  ((void)0)\n#define mi_stat_counter_increase(stat,amount) ((void)0)\n#endif\n\n#define mi_heap_stat_counter_increase(heap,stat,amount)  mi_stat_counter_increase( (heap)->tld->stats.stat, amount)\n#define mi_heap_stat_increase(heap,stat,amount)  mi_stat_increase( (heap)->tld->stats.stat, amount)\n#define mi_heap_stat_decrease(heap,stat,amount)  mi_stat_decrease( (heap)->tld->stats.stat, amount)\n#define mi_heap_stat_adjust_decrease(heap,stat,amount)  mi_stat_adjust_decrease( (heap)->tld->stats.stat, amount)\n\n#endif\n"
  },
  {
    "path": "include/mimalloc-new-delete.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_NEW_DELETE_H\n#define MIMALLOC_NEW_DELETE_H\n\n// ----------------------------------------------------------------------------\n// This header provides convenient overrides for the new and\n// delete operations in C++.\n//\n// This header should be included in only one source file!\n//\n// On Windows, or when linking dynamically with mimalloc, these\n// can be more performant than the standard new-delete operations.\n// See <https://en.cppreference.com/w/cpp/memory/new/operator_new>\n// ---------------------------------------------------------------------------\n#if defined(__cplusplus)\n  #include <new>\n  #include <mimalloc.h>\n\n  #if defined(_MSC_VER) && defined(_Ret_notnull_) && defined(_Post_writable_byte_size_)\n  // stay consistent with VCRT definitions\n  #define mi_decl_new(n)          mi_decl_nodiscard mi_decl_restrict _Ret_notnull_ _Post_writable_byte_size_(n)\n  #define mi_decl_new_nothrow(n)  mi_decl_nodiscard mi_decl_restrict _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(n)\n  #else\n  #define mi_decl_new(n)          mi_decl_nodiscard mi_decl_restrict\n  #define mi_decl_new_nothrow(n)  mi_decl_nodiscard mi_decl_restrict\n  #endif\n\n  void operator delete(void* p) noexcept              { mi_free(p); };\n  void operator delete[](void* p) noexcept            { mi_free(p); };\n\n  void operator delete  (void* p, const std::nothrow_t&) noexcept { mi_free(p); }\n  void operator delete[](void* p, const std::nothrow_t&) noexcept { mi_free(p); }\n\n  mi_decl_new(n) void* operator new(std::size_t n) noexcept(false) { return mi_new(n); }\n  mi_decl_new(n) void* operator new[](std::size_t n) noexcept(false) { return mi_new(n); }\n\n  mi_decl_new_nothrow(n) void* operator new  (std::size_t n, const std::nothrow_t& tag) noexcept { (void)(tag); return mi_new_nothrow(n); }\n  mi_decl_new_nothrow(n) void* operator new[](std::size_t n, const std::nothrow_t& tag) noexcept { (void)(tag); return mi_new_nothrow(n); }\n\n  #if (__cplusplus >= 201402L || _MSC_VER >= 1916)\n  void operator delete  (void* p, std::size_t n) noexcept { mi_free_size(p,n); };\n  void operator delete[](void* p, std::size_t n) noexcept { mi_free_size(p,n); };\n  #endif\n\n  #if (__cplusplus > 201402L || defined(__cpp_aligned_new))\n  void operator delete  (void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete[](void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete  (void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast<size_t>(al)); };\n  void operator delete[](void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast<size_t>(al)); };\n  void operator delete  (void* p, std::align_val_t al, const std::nothrow_t&) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete[](void* p, std::align_val_t al, const std::nothrow_t&) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n\n  void* operator new  (std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast<size_t>(al)); }\n  void* operator new[](std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast<size_t>(al)); }\n  void* operator new  (std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast<size_t>(al)); }\n  void* operator new[](std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast<size_t>(al)); }\n  #endif\n#endif\n\n#endif // MIMALLOC_NEW_DELETE_H\n"
  },
  {
    "path": "include/mimalloc-override.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_OVERRIDE_H\n#define MIMALLOC_OVERRIDE_H\n\n/* ----------------------------------------------------------------------------\nThis header can be used to statically redirect malloc/free and new/delete\nto the mimalloc variants. This can be useful if one can include this file on\neach source file in a project (but be careful when using external code to\nnot accidentally mix pointers from different allocators).\n-----------------------------------------------------------------------------*/\n\n#include <mimalloc.h>\n\n// Standard C allocation\n#define malloc(n)               mi_malloc(n)\n#define calloc(n,c)             mi_calloc(n,c)\n#define realloc(p,n)            mi_realloc(p,n)\n#define free(p)                 mi_free(p)\n\n#define strdup(s)               mi_strdup(s)\n#define strndup(s,n)            mi_strndup(s,n)\n#define realpath(f,n)           mi_realpath(f,n)\n\n// Microsoft extensions\n#define _expand(p,n)            mi_expand(p,n)\n#define _msize(p)               mi_usable_size(p)\n#define _recalloc(p,n,c)        mi_recalloc(p,n,c)\n\n#define _strdup(s)              mi_strdup(s)\n#define _strndup(s,n)           mi_strndup(s,n)\n#define _wcsdup(s)              (wchar_t*)mi_wcsdup((const unsigned short*)(s))\n#define _mbsdup(s)              mi_mbsdup(s)\n#define _dupenv_s(b,n,v)        mi_dupenv_s(b,n,v)\n#define _wdupenv_s(b,n,v)       mi_wdupenv_s((unsigned short*)(b),n,(const unsigned short*)(v))\n\n// Various Posix and Unix variants\n#define reallocf(p,n)           mi_reallocf(p,n)\n#define malloc_size(p)          mi_usable_size(p)\n#define malloc_usable_size(p)   mi_usable_size(p)\n#define malloc_good_size(sz)    mi_malloc_good_size(sz)\n#define cfree(p)                mi_free(p)\n\n#define valloc(n)               mi_valloc(n)\n#define pvalloc(n)              mi_pvalloc(n)\n#define reallocarray(p,s,n)     mi_reallocarray(p,s,n)\n#define reallocarr(p,s,n)       mi_reallocarr(p,s,n)\n#define memalign(a,n)           mi_memalign(a,n)\n#define aligned_alloc(a,n)      mi_aligned_alloc(a,n)\n#define posix_memalign(p,a,n)   mi_posix_memalign(p,a,n)\n#define _posix_memalign(p,a,n)  mi_posix_memalign(p,a,n)\n\n// Microsoft aligned variants\n#define _aligned_malloc(n,a)                  mi_malloc_aligned(n,a)\n#define _aligned_realloc(p,n,a)               mi_realloc_aligned(p,n,a)\n#define _aligned_recalloc(p,s,n,a)            mi_aligned_recalloc(p,s,n,a)\n#define _aligned_msize(p,a,o)                 mi_usable_size(p)\n#define _aligned_free(p)                      mi_free(p)\n#define _aligned_offset_malloc(n,a,o)         mi_malloc_aligned_at(n,a,o)\n#define _aligned_offset_realloc(p,n,a,o)      mi_realloc_aligned_at(p,n,a,o)\n#define _aligned_offset_recalloc(p,s,n,a,o)   mi_recalloc_aligned_at(p,s,n,a,o)\n\n#endif // MIMALLOC_OVERRIDE_H\n"
  },
  {
    "path": "include/mimalloc-stats.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2025, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_STATS_H\n#define MIMALLOC_STATS_H\n\n#include <mimalloc.h>\n#include <stdint.h>\n\n#define MI_STAT_VERSION   4  // increased on every backward incompatible change\n\n// count allocation over time\ntypedef struct mi_stat_count_s {\n  int64_t total;                              // total allocated\n  int64_t peak;                               // peak allocation\n  int64_t current;                            // current allocation\n} mi_stat_count_t;\n\n// counters only increase\ntypedef struct mi_stat_counter_s {\n  int64_t total;                              // total count\n} mi_stat_counter_t;\n\n#define MI_STAT_FIELDS() \\\n  MI_STAT_COUNT(pages)                      /* count of mimalloc pages */ \\\n  MI_STAT_COUNT(reserved)                   /* reserved memory bytes */ \\\n  MI_STAT_COUNT(committed)                  /* committed bytes */ \\\n  MI_STAT_COUNTER(reset)                    /* reset bytes */ \\\n  MI_STAT_COUNTER(purged)                   /* purged bytes */ \\\n  MI_STAT_COUNT(page_committed)             /* committed memory inside pages */ \\\n  MI_STAT_COUNT(pages_abandoned)            /* abandonded pages count */ \\\n  MI_STAT_COUNT(threads)                    /* number of threads */ \\\n  MI_STAT_COUNT(malloc_normal)              /* allocated bytes <= MI_LARGE_OBJ_SIZE_MAX */ \\\n  MI_STAT_COUNT(malloc_huge)                /* allocated bytes in huge pages */ \\\n  MI_STAT_COUNT(malloc_requested)           /* malloc requested bytes */ \\\n  \\\n  MI_STAT_COUNTER(mmap_calls) \\\n  MI_STAT_COUNTER(commit_calls) \\\n  MI_STAT_COUNTER(reset_calls) \\\n  MI_STAT_COUNTER(purge_calls) \\\n  MI_STAT_COUNTER(arena_count)              /* number of memory arena's */ \\\n  MI_STAT_COUNTER(malloc_normal_count)      /* number of blocks <= MI_LARGE_OBJ_SIZE_MAX */ \\\n  MI_STAT_COUNTER(malloc_huge_count)        /* number of huge bloks */ \\\n  MI_STAT_COUNTER(malloc_guarded_count)     /* number of allocations with guard pages */ \\\n  \\\n  /* internal statistics */ \\\n  MI_STAT_COUNTER(arena_rollback_count) \\\n  MI_STAT_COUNTER(arena_purges) \\\n  MI_STAT_COUNTER(pages_extended)           /* number of page extensions */ \\\n  MI_STAT_COUNTER(pages_retire)             /* number of pages that are retired */ \\\n  MI_STAT_COUNTER(page_searches)            /* total pages searched for a fresh page */ \\\n  MI_STAT_COUNTER(page_searches_count)      /* searched count for a fresh page */ \\\n  /* only on v1 and v2 */ \\\n  MI_STAT_COUNT(segments) \\\n  MI_STAT_COUNT(segments_abandoned) \\\n  MI_STAT_COUNT(segments_cache) \\\n  MI_STAT_COUNT(_segments_reserved) \\\n  /* only on v3 */ \\\n  MI_STAT_COUNTER(pages_reclaim_on_alloc) \\\n  MI_STAT_COUNTER(pages_reclaim_on_free) \\\n  MI_STAT_COUNTER(pages_reabandon_full) \\\n  MI_STAT_COUNTER(pages_unabandon_busy_wait) \\\n\n\n// Define the statistics structure\n#define MI_BIN_HUGE             (73U)   // see types.h\n#define MI_STAT_COUNT(stat)     mi_stat_count_t stat;\n#define MI_STAT_COUNTER(stat)   mi_stat_counter_t stat;\n\ntypedef struct mi_stats_s\n{\n  size_t size;          // size of the mi_stats_t structure \n  size_t version;       \n\n  MI_STAT_FIELDS()\n\n  // future extension\n  mi_stat_count_t   _stat_reserved[4];\n  mi_stat_counter_t _stat_counter_reserved[4];\n\n  // size segregated statistics\n  mi_stat_count_t   malloc_bins[MI_BIN_HUGE+1];   // allocation per size bin\n  mi_stat_count_t   page_bins[MI_BIN_HUGE+1];     // pages allocated per size bin\n} mi_stats_t;\n\n#undef MI_STAT_COUNT\n#undef MI_STAT_COUNTER\n\n// helper\n#define mi_stats_t_decl(name)  mi_stats_t name = { 0 }; name.size = sizeof(mi_stats_t); name.version = MI_STAT_VERSION;\n\n// Exported definitions\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nmi_decl_export bool  mi_stats_get( mi_stats_t* stats ) mi_attr_noexcept;\nmi_decl_export char* mi_stats_get_json( size_t buf_size, char* buf ) mi_attr_noexcept;    // use mi_free to free the result if the input buf == NULL\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // MIMALLOC_STATS_H\n"
  },
  {
    "path": "include/mimalloc.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2026, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#pragma once\n#ifndef MIMALLOC_H\n#define MIMALLOC_H\n\n#define MI_MALLOC_VERSION 227  // major + 2 digits minor\n\n// ------------------------------------------------------\n// Compiler specific attributes\n// ------------------------------------------------------\n\n#ifdef __cplusplus\n  #if (__cplusplus >= 201103L) || (_MSC_VER > 1900)  // C++11\n    #define mi_attr_noexcept   noexcept\n  #else\n    #define mi_attr_noexcept   throw()\n  #endif\n#else\n  #define mi_attr_noexcept\n#endif\n\n#if defined(__cplusplus) && (__cplusplus >= 201703)\n  #define mi_decl_nodiscard    [[nodiscard]]\n#elif (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__)  // includes clang, icc, and clang-cl\n  #define mi_decl_nodiscard    __attribute__((warn_unused_result))\n#elif defined(_HAS_NODISCARD)\n  #define mi_decl_nodiscard    _NODISCARD\n#elif (_MSC_VER >= 1700)\n  #define mi_decl_nodiscard    _Check_return_\n#else\n  #define mi_decl_nodiscard\n#endif\n\n#if defined(_MSC_VER) || defined(__MINGW32__)\n  #if !defined(MI_SHARED_LIB)\n    #define mi_decl_export\n  #elif defined(MI_SHARED_LIB_EXPORT)\n    #define mi_decl_export              __declspec(dllexport)\n  #else\n    #define mi_decl_export              __declspec(dllimport)\n  #endif\n  #if defined(__MINGW32__)\n    #define mi_decl_restrict\n    #define mi_attr_malloc              __attribute__((malloc))\n  #else\n    #if (_MSC_VER >= 1900) && !defined(__EDG__)\n      #define mi_decl_restrict          __declspec(allocator) __declspec(restrict)\n    #else\n      #define mi_decl_restrict          __declspec(restrict)\n    #endif\n    #define mi_attr_malloc\n  #endif\n  #define mi_cdecl                      __cdecl\n  #define mi_attr_alloc_size(s)\n  #define mi_attr_alloc_size2(s1,s2)\n  #define mi_attr_alloc_align(p)\n#elif defined(__GNUC__)                 // includes clang and icc\n  #if defined(MI_SHARED_LIB) && defined(MI_SHARED_LIB_EXPORT)\n    #define mi_decl_export              __attribute__((visibility(\"default\")))\n  #else\n    #define mi_decl_export\n  #endif\n  #define mi_cdecl                      // leads to warnings... __attribute__((cdecl))\n  #define mi_decl_restrict\n  #define mi_attr_malloc                __attribute__((malloc))\n  #if (defined(__clang_major__) && (__clang_major__ < 4)) || (__GNUC__ < 5)\n    #define mi_attr_alloc_size(s)\n    #define mi_attr_alloc_size2(s1,s2)\n    #define mi_attr_alloc_align(p)\n  #elif defined(__INTEL_COMPILER)\n    #define mi_attr_alloc_size(s)       __attribute__((alloc_size(s)))\n    #define mi_attr_alloc_size2(s1,s2)  __attribute__((alloc_size(s1,s2)))\n    #define mi_attr_alloc_align(p)\n  #else\n    #define mi_attr_alloc_size(s)       __attribute__((alloc_size(s)))\n    #define mi_attr_alloc_size2(s1,s2)  __attribute__((alloc_size(s1,s2)))\n    #define mi_attr_alloc_align(p)      __attribute__((alloc_align(p)))\n  #endif\n#else\n  #define mi_cdecl\n  #define mi_decl_export\n  #define mi_decl_restrict\n  #define mi_attr_malloc\n  #define mi_attr_alloc_size(s)\n  #define mi_attr_alloc_size2(s1,s2)\n  #define mi_attr_alloc_align(p)\n#endif\n\n// ------------------------------------------------------\n// Includes\n// ------------------------------------------------------\n\n#include <stddef.h>     // size_t\n#include <stdbool.h>    // bool\n#include <stdint.h>     // INTPTR_MAX\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// ------------------------------------------------------\n// Standard malloc interface\n// ------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc(size_t size)  mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc(size_t count, size_t size)  mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2);\nmi_decl_nodiscard mi_decl_export void* mi_realloc(void* p, size_t newsize)      mi_attr_noexcept mi_attr_alloc_size(2);\nmi_decl_export void* mi_expand(void* p, size_t newsize)                         mi_attr_noexcept mi_attr_alloc_size(2);\n\nmi_decl_export void mi_free(void* p) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept mi_attr_malloc;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept mi_attr_malloc;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc;\n\n// ------------------------------------------------------\n// Extended functionality\n// ------------------------------------------------------\n#define MI_SMALL_WSIZE_MAX  (128)\n#define MI_SMALL_SIZE_MAX   (MI_SMALL_WSIZE_MAX*sizeof(void*))\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc(size_t size)       mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2);\nmi_decl_nodiscard mi_decl_export void* mi_reallocn(void* p, size_t count, size_t size)        mi_attr_noexcept mi_attr_alloc_size2(2,3);\nmi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize)                   mi_attr_noexcept mi_attr_alloc_size(2);\n\nmi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size)     mi_attr_noexcept;\n\n\n// ------------------------------------------------------\n// Internals\n// ------------------------------------------------------\n\ntypedef void (mi_cdecl mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg);\nmi_decl_export void mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg) mi_attr_noexcept;\n\ntypedef void (mi_cdecl mi_output_fun)(const char* msg, void* arg);\nmi_decl_export void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept;\n\ntypedef void (mi_cdecl mi_error_fun)(int err, void* arg);\nmi_decl_export void mi_register_error(mi_error_fun* fun, void* arg);\n\nmi_decl_export void mi_collect(bool force)    mi_attr_noexcept;\nmi_decl_export int  mi_version(void)          mi_attr_noexcept;\nmi_decl_export void mi_stats_reset(void)      mi_attr_noexcept;\nmi_decl_export void mi_stats_merge(void)      mi_attr_noexcept;\nmi_decl_export void mi_stats_print(void* out) mi_attr_noexcept;  // backward compatibility: `out` is ignored and should be NULL\nmi_decl_export void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept;\nmi_decl_export void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept;\nmi_decl_export void mi_options_print(void)    mi_attr_noexcept;\n\nmi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs,\n                                    size_t* current_rss, size_t* peak_rss,\n                                    size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept;\n\n\n// Generally do not use the following as these are usually called automatically\nmi_decl_export void mi_process_init(void)     mi_attr_noexcept;\nmi_decl_export void mi_cdecl mi_process_done(void) mi_attr_noexcept;\nmi_decl_export void mi_thread_init(void)      mi_attr_noexcept;\nmi_decl_export void mi_thread_done(void)      mi_attr_noexcept;\n\n\n// -------------------------------------------------------------------------------------\n// Aligned allocation\n// Note that `alignment` always follows `size` for consistency with unaligned\n// allocation, but unfortunately this differs from `posix_memalign` and `aligned_alloc`.\n// -------------------------------------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2) mi_attr_alloc_align(3);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2);\nmi_decl_nodiscard mi_decl_export void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3);\nmi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2);\n\n\n// -----------------------------------------------------------------\n// Return allocated block size (if the return value is not NULL)\n// -----------------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc(size_t size, size_t* block_size)  mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* block_size)  mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2);\nmi_decl_nodiscard mi_decl_export void* mi_urealloc(void* p, size_t newsize, size_t* block_size_pre, size_t* block_size_post) mi_attr_noexcept mi_attr_alloc_size(2);\nmi_decl_export void mi_ufree(void* p, size_t* block_size) mi_attr_noexcept;\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\n\n\n// -------------------------------------------------------------------------------------\n// Heaps: first-class, but can only allocate from the same thread that created it.\n// -------------------------------------------------------------------------------------\n\nstruct mi_heap_s;\ntypedef struct mi_heap_s mi_heap_t;\n\nmi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new(void);\nmi_decl_export void       mi_heap_delete(mi_heap_t* heap);\nmi_decl_export void       mi_heap_destroy(mi_heap_t* heap);\nmi_decl_export mi_heap_t* mi_heap_set_default(mi_heap_t* heap);\nmi_decl_export mi_heap_t* mi_heap_get_default(void);\nmi_decl_export mi_heap_t* mi_heap_get_backing(void);\nmi_decl_export void       mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept;\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);\n\nmi_decl_nodiscard mi_decl_export void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize)              mi_attr_noexcept mi_attr_alloc_size(3);\nmi_decl_nodiscard mi_decl_export void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size)  mi_attr_noexcept mi_attr_alloc_size2(3,4);\nmi_decl_nodiscard mi_decl_export void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize)             mi_attr_noexcept mi_attr_alloc_size(3);\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s)            mi_attr_noexcept mi_attr_malloc;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept mi_attr_malloc;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc;\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3) mi_attr_alloc_align(4);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3);\nmi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4);\nmi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3);\n\n\n// --------------------------------------------------------------------------------\n// Zero initialized re-allocation.\n// Only valid on memory that was originally allocated with zero initialization too.\n// e.g. `mi_calloc`, `mi_zalloc`, `mi_zalloc_aligned` etc.\n// see <https://github.com/microsoft/mimalloc/issues/63#issuecomment-508272992>\n// --------------------------------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_export void* mi_rezalloc(void* p, size_t newsize)                mi_attr_noexcept mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export void* mi_recalloc(void* p, size_t newcount, size_t size)  mi_attr_noexcept mi_attr_alloc_size2(2,3);\n\nmi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3);\nmi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(2,3) mi_attr_alloc_align(4);\nmi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(2,3);\n\nmi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize)                mi_attr_noexcept mi_attr_alloc_size(3);\nmi_decl_nodiscard mi_decl_export void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size)  mi_attr_noexcept mi_attr_alloc_size2(3,4);\n\nmi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4);\nmi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3);\nmi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(3,4) mi_attr_alloc_align(5);\nmi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(3,4);\n\n\n// ------------------------------------------------------\n// Analysis\n// ------------------------------------------------------\n\nmi_decl_export bool mi_heap_contains_block(mi_heap_t* heap, const void* p);\nmi_decl_export bool mi_heap_check_owned(mi_heap_t* heap, const void* p);\nmi_decl_export bool mi_check_owned(const void* p);\n\n// An area of heap space contains blocks of a single size.\ntypedef struct mi_heap_area_s {\n  void*  blocks;      // start of the area containing heap blocks\n  size_t reserved;    // bytes reserved for this area (virtual)\n  size_t committed;   // current available bytes for this area\n  size_t used;        // number of allocated blocks\n  size_t block_size;  // size in bytes of each block\n  size_t full_block_size; // size in bytes of a full block including padding and metadata.\n  int    heap_tag;    // heap tag associated with this area\n} mi_heap_area_t;\n\ntypedef bool (mi_cdecl mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg);\n\nmi_decl_export bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);\n\n// Experimental\nmi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export bool mi_is_redirected(void) mi_attr_noexcept;\n\nmi_decl_export int   mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept;\nmi_decl_export int   mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept;\n\nmi_decl_export int   mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept;\nmi_decl_export bool  mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept;\n\nmi_decl_export void  mi_debug_show_arenas(void) mi_attr_noexcept;\nmi_decl_export void  mi_arenas_print(void) mi_attr_noexcept;\n\n// Experimental: heaps associated with specific memory arena's\ntypedef int mi_arena_id_t;\nmi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size);\nmi_decl_export int   mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept;\nmi_decl_export int   mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept;\nmi_decl_export bool  mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept;\n\n#if MI_MALLOC_VERSION >= 182\n// Create a heap that only allocates in the specified arena\nmi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id);\n#endif\n\n\n// Experimental: allow sub-processes whose memory areas stay separated (and no reclamation between them)\n// Used for example for separate interpreters in one process.\ntypedef void* mi_subproc_id_t;\nmi_decl_export mi_subproc_id_t mi_subproc_main(void);\nmi_decl_export mi_subproc_id_t mi_subproc_new(void);\nmi_decl_export void mi_subproc_delete(mi_subproc_id_t subproc);\nmi_decl_export void mi_subproc_add_current_thread(mi_subproc_id_t subproc); // this should be called right after a thread is created (and no allocation has taken place yet)\n\n// Experimental: visit abandoned heap areas (that are not owned by a specific heap)\nmi_decl_export bool mi_abandoned_visit_blocks(mi_subproc_id_t subproc_id, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);\n\n// Experimental: objects followed by a guard page.\n// A sample rate of 0 disables guarded objects, while 1 uses a guard page for every object.\n// A seed of 0 uses a random start point. Only objects within the size bound are eligable for guard pages.\nmi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed);\nmi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max);\n\n// Experimental: communicate that the thread is part of a threadpool\nmi_decl_export void mi_thread_set_in_threadpool(void) mi_attr_noexcept;\n\n// Experimental: create a new heap with a specified heap tag. Set `allow_destroy` to false to allow the thread\n// to reclaim abandoned memory (with a compatible heap_tag and arena_id) but in that case `mi_heap_destroy` will\n// fall back to `mi_heap_delete`.\nmi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id);\n\n// deprecated\nmi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept;\nmi_decl_export void mi_collect_reduce(size_t target_thread_owned) mi_attr_noexcept;\n\n\n\n// ------------------------------------------------------\n// Convenience\n// ------------------------------------------------------\n\n#define mi_malloc_tp(tp)                ((tp*)mi_malloc(sizeof(tp)))\n#define mi_zalloc_tp(tp)                ((tp*)mi_zalloc(sizeof(tp)))\n#define mi_calloc_tp(tp,n)              ((tp*)mi_calloc(n,sizeof(tp)))\n#define mi_mallocn_tp(tp,n)             ((tp*)mi_mallocn(n,sizeof(tp)))\n#define mi_reallocn_tp(p,tp,n)          ((tp*)mi_reallocn(p,n,sizeof(tp)))\n#define mi_recalloc_tp(p,tp,n)          ((tp*)mi_recalloc(p,n,sizeof(tp)))\n\n#define mi_heap_malloc_tp(hp,tp)        ((tp*)mi_heap_malloc(hp,sizeof(tp)))\n#define mi_heap_zalloc_tp(hp,tp)        ((tp*)mi_heap_zalloc(hp,sizeof(tp)))\n#define mi_heap_calloc_tp(hp,tp,n)      ((tp*)mi_heap_calloc(hp,n,sizeof(tp)))\n#define mi_heap_mallocn_tp(hp,tp,n)     ((tp*)mi_heap_mallocn(hp,n,sizeof(tp)))\n#define mi_heap_reallocn_tp(hp,p,tp,n)  ((tp*)mi_heap_reallocn(hp,p,n,sizeof(tp)))\n#define mi_heap_recalloc_tp(hp,p,tp,n)  ((tp*)mi_heap_recalloc(hp,p,n,sizeof(tp)))\n\n\n// ------------------------------------------------------\n// Options\n// ------------------------------------------------------\n\ntypedef enum mi_option_e {\n  // stable options\n  mi_option_show_errors,                // print error messages\n  mi_option_show_stats,                 // print statistics on termination\n  mi_option_verbose,                    // print verbose messages\n  // advanced options\n  mi_option_eager_commit,               // eager commit segments? (after `eager_commit_delay` segments) (=1)\n  mi_option_arena_eager_commit,         // eager commit arenas? Use 2 to enable just on overcommit systems (=2)\n  mi_option_purge_decommits,            // should a memory purge decommit? (=1). Set to 0 to use memory reset on a purge (instead of decommit)\n  mi_option_allow_large_os_pages,       // allow use of large (2 or 4 MiB) OS pages, implies eager commit.\n  mi_option_reserve_huge_os_pages,      // reserve N huge OS pages (1GiB pages) at startup\n  mi_option_reserve_huge_os_pages_at,   // reserve huge OS pages at a specific NUMA node\n  mi_option_reserve_os_memory,          // reserve specified amount of OS memory in an arena at startup (internally, this value is in KiB; use `mi_option_get_size`)\n  mi_option_deprecated_segment_cache,\n  mi_option_deprecated_page_reset,\n  mi_option_abandoned_page_purge,       // immediately purge delayed purges on thread termination\n  mi_option_deprecated_segment_reset,\n  mi_option_eager_commit_delay,         // the first N segments per thread are not eagerly committed (but per page in the segment on demand)\n  mi_option_purge_delay,                // memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all. (=10)\n  mi_option_use_numa_nodes,             // 0 = use all available numa nodes, otherwise use at most N nodes.\n  mi_option_disallow_os_alloc,          // 1 = do not use OS memory for allocation (but only programmatically reserved arenas)\n  mi_option_os_tag,                     // tag used for OS logging (macOS only for now) (=100)\n  mi_option_max_errors,                 // issue at most N error messages\n  mi_option_max_warnings,               // issue at most N warning messages\n  mi_option_max_segment_reclaim,        // max. percentage of the abandoned segments can be reclaimed per try (=10%)\n  mi_option_destroy_on_exit,            // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe\n  mi_option_arena_reserve,              // initial memory size for arena reservation (= 1 GiB on 64-bit) (internally, this value is in KiB; use `mi_option_get_size`)\n  mi_option_arena_purge_mult,           // multiplier for `purge_delay` for the purging delay for arenas (=10)\n  mi_option_purge_extend_delay,\n  mi_option_abandoned_reclaim_on_free,  // allow to reclaim an abandoned segment on a free (=1)\n  mi_option_disallow_arena_alloc,       // 1 = do not use arena's for allocation (except if using specific arena id's)\n  mi_option_retry_on_oom,               // retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows)\n  mi_option_visit_abandoned,            // allow visiting heap blocks from abandoned threads (=0)\n  mi_option_guarded_min,                // only used when building with MI_GUARDED: minimal rounded object size for guarded objects (=0)\n  mi_option_guarded_max,                // only used when building with MI_GUARDED: maximal rounded object size for guarded objects (=0)\n  mi_option_guarded_precise,            // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)\n  mi_option_guarded_sample_rate,        // 1 out of N allocations in the min/max range will be guarded (=1000)\n  mi_option_guarded_sample_seed,        // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0)\n  mi_option_target_segments_per_thread, // experimental (=0)\n  mi_option_generic_collect,            // collect heaps every N (=10000) generic allocation calls\n  mi_option_allow_thp,                  // allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process.\n  _mi_option_last,\n  // legacy option names\n  mi_option_large_os_pages = mi_option_allow_large_os_pages,\n  mi_option_eager_region_commit = mi_option_arena_eager_commit,\n  mi_option_reset_decommits = mi_option_purge_decommits,\n  mi_option_reset_delay = mi_option_purge_delay,\n  mi_option_abandoned_page_reset = mi_option_abandoned_page_purge,\n  mi_option_limit_os_alloc = mi_option_disallow_os_alloc\n} mi_option_t;\n\n\nmi_decl_nodiscard mi_decl_export bool mi_option_is_enabled(mi_option_t option);\nmi_decl_export void mi_option_enable(mi_option_t option);\nmi_decl_export void mi_option_disable(mi_option_t option);\nmi_decl_export void mi_option_set_enabled(mi_option_t option, bool enable);\nmi_decl_export void mi_option_set_enabled_default(mi_option_t option, bool enable);\n\nmi_decl_nodiscard mi_decl_export long   mi_option_get(mi_option_t option);\nmi_decl_nodiscard mi_decl_export long   mi_option_get_clamp(mi_option_t option, long min, long max);\nmi_decl_nodiscard mi_decl_export size_t mi_option_get_size(mi_option_t option);\nmi_decl_export void mi_option_set(mi_option_t option, long value);\nmi_decl_export void mi_option_set_default(mi_option_t option, long value);\n\n\n// -------------------------------------------------------------------------------------------------------\n// \"mi\" prefixed implementations of various posix, Unix, Windows, and C++ allocation functions.\n// (This can be convenient when providing overrides of these functions as done in `mimalloc-override.h`.)\n// note: we use `mi_cfree` as \"checked free\" and it checks if the pointer is in our heap before free-ing.\n// -------------------------------------------------------------------------------------------------------\n\nmi_decl_export void  mi_cfree(void* p) mi_attr_noexcept;\nmi_decl_export void* mi__expand(void* p, size_t newsize) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p)        mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export size_t mi_malloc_good_size(size_t size)     mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept;\n\nmi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size)   mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size)  mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1);\n\nmi_decl_nodiscard mi_decl_export void* mi_reallocarray(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3);\nmi_decl_nodiscard mi_decl_export int   mi_reallocarr(void* p, size_t count, size_t size) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept;\nmi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept;\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept mi_attr_malloc;\nmi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char*  mi_mbsdup(const unsigned char* s)  mi_attr_noexcept mi_attr_malloc;\nmi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name)                      mi_attr_noexcept;\nmi_decl_export int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept;\n\nmi_decl_export void mi_free_size(void* p, size_t size)                           mi_attr_noexcept;\nmi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept;\nmi_decl_export void mi_free_aligned(void* p, size_t alignment)                   mi_attr_noexcept;\n\n// The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`.\n// (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception).\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new(size_t size)                   mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_nothrow(size_t size)           mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_n(size_t count, size_t size)   mi_attr_malloc mi_attr_alloc_size2(1, 2);\nmi_decl_nodiscard mi_decl_export void* mi_new_realloc(void* p, size_t newsize)                mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount, size_t size) mi_attr_alloc_size2(2, 3);\n\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size)                mi_attr_malloc mi_attr_alloc_size(2);\nmi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(2, 3);\n\n#ifdef __cplusplus\n}\n#endif\n\n// ---------------------------------------------------------------------------------------------\n// Implement the C++ std::allocator interface for use in STL containers.\n// (note: see `mimalloc-new-delete.h` for overriding the new/delete operators globally)\n// ---------------------------------------------------------------------------------------------\n#ifdef __cplusplus\n\n#include <cstddef>     // std::size_t\n#include <cstdint>     // PTRDIFF_MAX\n#if (__cplusplus >= 201103L) || (_MSC_VER > 1900)  // C++11\n#include <type_traits> // std::true_type\n#include <utility>     // std::forward\n#endif\n\ntemplate<class T> struct _mi_stl_allocator_common {\n  typedef T                 value_type;\n  typedef std::size_t       size_type;\n  typedef std::ptrdiff_t    difference_type;\n  typedef value_type&       reference;\n  typedef value_type const& const_reference;\n  typedef value_type*       pointer;\n  typedef value_type const* const_pointer;\n\n  #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900))  // C++11\n  using propagate_on_container_copy_assignment = std::true_type;\n  using propagate_on_container_move_assignment = std::true_type;\n  using propagate_on_container_swap            = std::true_type;\n  template <class U, class ...Args> void construct(U* p, Args&& ...args) { ::new(p) U(std::forward<Args>(args)...); }\n  template <class U> void destroy(U* p) mi_attr_noexcept { p->~U(); }\n  #else\n  void construct(pointer p, value_type const& val) { ::new(p) value_type(val); }\n  void destroy(pointer p) { p->~value_type(); }\n  #endif\n\n  size_type     max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); }\n  pointer       address(reference x) const        { return &x; }\n  const_pointer address(const_reference x) const  { return &x; }\n};\n\ntemplate<class T> struct mi_stl_allocator : public _mi_stl_allocator_common<T> {\n  using typename _mi_stl_allocator_common<T>::size_type;\n  using typename _mi_stl_allocator_common<T>::value_type;\n  using typename _mi_stl_allocator_common<T>::pointer;\n  template <class U> struct rebind { typedef mi_stl_allocator<U> other; };\n\n  mi_stl_allocator()                                             mi_attr_noexcept = default;\n  mi_stl_allocator(const mi_stl_allocator&)                      mi_attr_noexcept = default;\n  template<class U> mi_stl_allocator(const mi_stl_allocator<U>&) mi_attr_noexcept { }\n  mi_stl_allocator  select_on_container_copy_construction() const { return *this; }\n  void              deallocate(T* p, size_type) { mi_free(p); }\n\n  #if (__cplusplus >= 201703L)  // C++17\n  mi_decl_nodiscard T* allocate(size_type count) { return static_cast<T*>(mi_new_n(count, sizeof(T))); }\n  mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); }\n  #else\n  mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast<pointer>(mi_new_n(count, sizeof(value_type))); }\n  #endif\n\n  #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900))  // C++11\n  using is_always_equal = std::true_type;\n  #endif\n};\n\ntemplate<class T1,class T2> bool operator==(const mi_stl_allocator<T1>& , const mi_stl_allocator<T2>& ) mi_attr_noexcept { return true; }\ntemplate<class T1,class T2> bool operator!=(const mi_stl_allocator<T1>& , const mi_stl_allocator<T2>& ) mi_attr_noexcept { return false; }\n\n\n#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900)  // C++11\n#define MI_HAS_HEAP_STL_ALLOCATOR 1\n\n#include <memory>      // std::shared_ptr\n\n// Common base class for STL allocators in a specific heap\ntemplate<class T, bool _mi_destroy> struct _mi_heap_stl_allocator_common : public _mi_stl_allocator_common<T> {\n  using typename _mi_stl_allocator_common<T>::size_type;\n  using typename _mi_stl_allocator_common<T>::value_type;\n  using typename _mi_stl_allocator_common<T>::pointer;\n\n  _mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp, [](mi_heap_t*) {}) {}    /* will not delete nor destroy the passed in heap */\n\n  #if (__cplusplus >= 201703L)  // C++17\n  mi_decl_nodiscard T* allocate(size_type count) { return static_cast<T*>(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); }\n  mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); }\n  #else\n  mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast<pointer>(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(value_type))); }\n  #endif\n\n  #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900))  // C++11\n  using is_always_equal = std::false_type;\n  #endif\n\n  void collect(bool force) { mi_heap_collect(this->heap.get(), force); }\n  template<class U> bool is_equal(const _mi_heap_stl_allocator_common<U, _mi_destroy>& x) const { return (this->heap == x.heap); }\n\nprotected:\n  std::shared_ptr<mi_heap_t> heap;\n  template<class U, bool D> friend struct _mi_heap_stl_allocator_common;\n\n  _mi_heap_stl_allocator_common() {\n    mi_heap_t* hp = mi_heap_new();\n    this->heap.reset(hp, (_mi_destroy ? &heap_destroy : &heap_delete));  /* calls heap_delete/destroy when the refcount drops to zero */\n  }\n  _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { }\n  template<class U> _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common<U, _mi_destroy>& x) mi_attr_noexcept : heap(x.heap) { }\n\nprivate:\n  static void heap_delete(mi_heap_t* hp)  { if (hp != NULL) { mi_heap_delete(hp); } }\n  static void heap_destroy(mi_heap_t* hp) { if (hp != NULL) { mi_heap_destroy(hp); } }\n};\n\n// STL allocator allocation in a specific heap\ntemplate<class T> struct mi_heap_stl_allocator : public _mi_heap_stl_allocator_common<T, false> {\n  using typename _mi_heap_stl_allocator_common<T, false>::size_type;\n  mi_heap_stl_allocator() : _mi_heap_stl_allocator_common<T, false>() { } // creates fresh heap that is deleted when the destructor is called\n  mi_heap_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common<T, false>(hp) { }  // no delete nor destroy on the passed in heap\n  template<class U> mi_heap_stl_allocator(const mi_heap_stl_allocator<U>& x) mi_attr_noexcept : _mi_heap_stl_allocator_common<T, false>(x) { }\n\n  mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; }\n  void deallocate(T* p, size_type) { mi_free(p); }\n  template<class U> struct rebind { typedef mi_heap_stl_allocator<U> other; };\n};\n\ntemplate<class T1, class T2> bool operator==(const mi_heap_stl_allocator<T1>& x, const mi_heap_stl_allocator<T2>& y) mi_attr_noexcept { return (x.is_equal(y)); }\ntemplate<class T1, class T2> bool operator!=(const mi_heap_stl_allocator<T1>& x, const mi_heap_stl_allocator<T2>& y) mi_attr_noexcept { return (!x.is_equal(y)); }\n\n\n// STL allocator allocation in a specific heap, where `free` does nothing and\n// the heap is destroyed in one go on destruction -- use with care!\ntemplate<class T> struct mi_heap_destroy_stl_allocator : public _mi_heap_stl_allocator_common<T, true> {\n  using typename _mi_heap_stl_allocator_common<T, true>::size_type;\n  mi_heap_destroy_stl_allocator() : _mi_heap_stl_allocator_common<T, true>() { } // creates fresh heap that is destroyed when the destructor is called\n  mi_heap_destroy_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common<T, true>(hp) { }  // no delete nor destroy on the passed in heap\n  template<class U> mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator<U>& x) mi_attr_noexcept : _mi_heap_stl_allocator_common<T, true>(x) { }\n\n  mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; }\n  void deallocate(T*, size_type) { /* do nothing as we destroy the heap on destruct. */ }\n  template<class U> struct rebind { typedef mi_heap_destroy_stl_allocator<U> other; };\n};\n\ntemplate<class T1, class T2> bool operator==(const mi_heap_destroy_stl_allocator<T1>& x, const mi_heap_destroy_stl_allocator<T2>& y) mi_attr_noexcept { return (x.is_equal(y)); }\ntemplate<class T1, class T2> bool operator!=(const mi_heap_destroy_stl_allocator<T1>& x, const mi_heap_destroy_stl_allocator<T2>& y) mi_attr_noexcept { return (!x.is_equal(y)); }\n\n#endif // C++11\n\n#endif // __cplusplus\n\n#endif\n"
  },
  {
    "path": "mimalloc.pc.in",
    "content": "prefix=@CMAKE_INSTALL_PREFIX@\nlibdir=@mi_pc_libdir@\nincludedir=@mi_pc_includedir@\n\nName: @PROJECT_NAME@\nDescription: A compact general purpose allocator with excellent performance\nVersion: @PACKAGE_VERSION@\nURL: https://github.com/microsoft/mimalloc/\nLibs: -L${libdir} -l@mi_libname@\nLibs.private: @mi_pc_libraries@\nCflags: -I${includedir}\n"
  },
  {
    "path": "readme.md",
    "content": "\n<img align=\"left\" width=\"100\" height=\"100\" src=\"doc/mimalloc-logo.png\"/>\n\n[<img align=\"right\" src=\"https://dev.azure.com/Daan0324/mimalloc/_apis/build/status/microsoft.mimalloc?branchName=dev3\"/>](https://dev.azure.com/Daan0324/mimalloc/_build?definitionId=1&_a=summary)\n\n# mimalloc\n\n&nbsp;\n\nmimalloc (pronounced \"me-malloc\")\nis a general purpose allocator with excellent [performance](#performance) characteristics.\nInitially developed by Daan Leijen for the runtime systems of the\n[Koka](https://koka-lang.github.io) and [Lean](https://github.com/leanprover/lean) languages.\n\nLatest release   : `v3.2.8` (2026-02-03) release candidate 3, please report any issues.  \nLatest v2 release: `v2.2.7` (2026-01-15).  \nLatest v1 release: `v1.9.7` (2026-01-15).\n\nmimalloc is a drop-in replacement for `malloc` and can be used in other programs\nwithout code changes, for example, on dynamically linked ELF-based systems (Linux, BSD, etc.) you can use it as:\n```\n> LD_PRELOAD=/usr/lib/libmimalloc.so  myprogram\n```\nIt also includes a way to dynamically override the default allocator in [Windows](#override_on_windows). \nNotable aspects of the design include:\n\n- __small and consistent__: the library is about 10k LOC using simple and\n  consistent data structures. This makes it very suitable\n  to integrate and adapt in other projects. For runtime systems it\n  provides hooks for a monotonic _heartbeat_ and deferred freeing (for\n  bounded worst-case times with reference counting).\n  Partly due to its simplicity, mimalloc has been ported to many systems (Windows, macOS,\n  Linux, WASM, various BSD's, Haiku, MUSL, etc) and has excellent support for dynamic overriding.\n  At the same time, it is an industrial strength allocator that runs (very) large scale\n  distributed services on thousands of machines with excellent worst case latencies.\n- __free list sharding__: instead of one big free list (per size class) we have\n  many smaller lists per \"mimalloc page\" which reduces fragmentation and\n  increases locality --\n  things that are allocated close in time get allocated close in memory.\n  (A mimalloc page contains blocks of one size class and is usually 64KiB on a 64-bit system).\n- __free list multi-sharding__: the big idea! Not only do we shard the free list\n  per mimalloc page, but for each page we have multiple free lists. In particular, there\n  is one list for thread-local `free` operations, and another one for concurrent `free`\n  operations. Free-ing from another thread can now be a single CAS without needing\n  sophisticated coordination between threads. Since there will be\n  thousands of separate free lists, contention is naturally distributed over the heap,\n  and the chance of contending on a single location will be low -- this is quite\n  similar to randomized algorithms like skip lists where adding\n  a random oracle removes the need for a more complex algorithm.\n- __eager page purging__: when a \"page\" becomes empty (with increased chance\n  due to free list sharding) the memory is marked to the OS as unused (reset or decommitted)\n  reducing (real) memory pressure and fragmentation, especially in long running\n  programs.\n- __secure__: _mimalloc_ can be built in secure mode, adding guard pages,\n  randomized allocation, encrypted free lists, etc. to protect against various\n  heap vulnerabilities. The performance penalty is usually around 10% on average\n  over our benchmarks.\n- __first-class heaps__: efficiently create and use multiple heaps to allocate across different regions.\n  A heap can be destroyed at once instead of deallocating each object separately.\n  New: v3 has true first-class heaps where one can allocate in a heap from any thread.   \n- __bounded__: it does not suffer from _blowup_ \\[1\\], has bounded worst-case allocation\n  times (_wcat_) (upto OS primitives), bounded space overhead (~0.2% meta-data, with low\n  internal fragmentation), and has no internal points of contention using only atomic operations.\n- __fast__: In our benchmarks (see [below](#performance)),\n  _mimalloc_ outperforms other leading allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc),\n  and often uses less memory. A nice property is that it does consistently well over a wide range\n  of benchmarks. There is also good huge OS page support for larger server programs.\n\nThe [documentation](https://microsoft.github.io/mimalloc) gives a full overview of the API.\nYou can read more on the design of mimalloc in the [technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action) which also has detailed benchmark results.\n\nEnjoy!\n\n### Versions\n\nThere are three maintained versions of mimalloc. These are mostly equal except for how the OS memory is handled. \nNew development is mostly on v3, while v1 and v2 are maintained with security and bug fixes. \n\n- __v1__: initial design of mimalloc (release tags: `v1.9.x`, development branch `dev`). Send PR's against this version if possible.\n- __v2__: main mimalloc version. Uses thread-local segments to reduce fragmentation. (release tags: `v2.2.x`, development branch `dev2` and `main`)\n- __v3__: simplifies the lock-free design of previous versions and improves sharing of \n        memory between threads. On certain large workloads this version may use \n        (much) less memory. Also supports true first-class heaps (that can allocate from any thread) \n        and has more efficient heap-walking (for the CPython GC for example).\n        (release tags: `v3.2.x`, development branch `dev3`).\n\n### Releases\n\n* 2026-02-03, `v3.2.8` (rc3): Fix thread reinitialize issue on macOS. Fix SIMD codegen bug on older\n  GCC versions. Extend Windows TLS slot limit from 64 to 1088. Report commit statistics more precise.\n  Fixes issue in free-page search in arenas.\n* 2026-01-15, `v1.9.7`, `v2.2.7`, `v3.2.7` (rc2): Fix zero initializing blocks that were OS allocated.  \n  For v3 various bug and performance fixes. Fix Debian 32-bit compilation.\n* 2026-01-08, `v1.9.6`, `v2.2.6`, `v3.2.6` (rc1): Important bug fixes. Many improvements to v3 including \n  true first-class heaps where one can allocate in heap from any thread, and track statistics per heap as well.\n  Added `MIMALLOC_ALLOW_THP` option. This is by default enabled except on Android. When THP is detected on v3,\n  mimalloc will set the `MIMALLOC_MINIMAL_PURGE_SIZE` to 2MiB to avoid breaking up potential THP huge pages.\n  v3 uses faster TLS access on Windows, and has improved performance for `mi_calloc` and aligned allocations.\n  Fixed rare race condition on older v3, fixed potential buffer overflow in debug statistics, add API for returning\n  allocated sizes on allocation and free.\n* 2025-06-13, `v3.1.5`: Bug fix release where memory was not always correctly committed (issue #1098).\n* 2025-06-09, `v1.9.4`, `v2.2.4`, `v3.1.4` (beta) : Some important bug fixes, including a case where OS memory\n  was not always fully released. Improved v3 performance, build on XBox, fix build on Android, support interpose \n  for older macOS versions, use MADV_FREE_REUSABLE on macOS, always check commit success, better support for Windows \n  fixed TLS offset, etc.\n* 2025-03-28, `v1.9.3`, `v2.2.3`, `v3.0.3` (beta) : Various small bug and build fixes, including:\n  fix arm32 pre v7 builds, fix mingw build, get runtime statistics, improve statistic commit counts, \n  fix execution on non BMI1 x64 systems. \n* 2025-03-06, `v1.9.2`, `v2.2.2`, `v3.0.2-beta`: Various small bug and build fixes. \n  Add `mi_options_print`, `mi_arenas_print`, and the experimental `mi_stat_get` and `mi_stat_get_json`. \n  Add `mi_thread_set_in_threadpool` and `mi_heap_set_numa_affinity` (v3 only). Add vcpkg portfile. \n  Upgrade mimalloc-redirect to v1.3.2. `MI_OPT_ARCH` is off by default now but still assumes armv8.1-a on arm64\n  for fast atomic operations. Add QNX support.\n* 2025-01-03, `v1.8.9`, `v2.1.9`, `v3.0.1-alpha`: Interim release. Support Windows arm64. New [guarded](#guarded) build that can place OS \n  guard pages behind objects to catch buffer overflows as they occur. \n  Many small fixes: build on Windows arm64, cygwin, riscV, and dragonfly; fix Windows static library initialization to account for\n  thread local destructors (in Rust/C++); macOS tag change; macOS TLS slot fix; improve stats; \n  consistent `mimalloc.dll` on Windows (instead of `mimalloc-override.dll`); fix mimalloc-redirect on Win11 H2; \n  add 0-byte to canary; upstream CPython fixes; reduce .bss size; allow fixed TLS slot on Windows for improved performance.\n\n* [Older release notes](#older-release-notes)\n\nSpecial thanks to:\n\n* Sergiy Kuryata for his contributions on reducing memory commit -- especially on Windows with the Windows thread pool (now implemented in v3).\n* [David Carlier](https://devnexen.blogspot.com/) (@devnexen) for his many contributions, and making\n  mimalloc work better on many less common operating systems, like Haiku, Dragonfly, etc.\n* Mary Feofanova (@mary3000), Evgeniy Moiseenko, and Manuel Pöter (@mpoeter) for making mimalloc TSAN checkable, and finding\n  memory model bugs using the [genMC] model checker.\n* Weipeng Liu (@pongba), Zhuowei Li, Junhua Wang, and Jakub Szymanski, for their early support of mimalloc and deployment\n  at large scale services, leading to many improvements in the mimalloc algorithms for large workloads.\n* Jason Gibson (@jasongibson) for exhaustive testing on large scale workloads and server environments, and finding complex bugs\n  in (early versions of) `mimalloc`.\n* Manuel Pöter (@mpoeter) and Sam Gross(@colesbury) for finding an ABA concurrency issue in abandoned segment reclamation. Sam also created the [no GIL](https://github.com/colesbury/nogil) Python fork which\n  uses mimalloc internally.\n\n\n[genMC]: https://plv.mpi-sws.org/genmc/\n\n### Usage\n\nmimalloc is used in various large scale low-latency services and programs, for example:\n\n<a href=\"https://www.bing.com\"><img height=\"50\" align=\"left\" src=\"https://upload.wikimedia.org/wikipedia/commons/e/e9/Bing_logo.svg\"></a>\n<a href=\"https://azure.microsoft.com/\"><img height=\"50\" align=\"left\" src=\"https://upload.wikimedia.org/wikipedia/commons/a/a8/Microsoft_Azure_Logo.svg\"></a>\n<a href=\"https://learn.microsoft.com/en-us/azure/cosmos-db/\"><img height=\"100\" align=\"left\" src=\"https://miro.medium.com/v2/1*TcATfAZhqi8TlZyx4vZAHg.png\" style=\"border-radius:10px\">\n<a href=\"https://deathstrandingpc.505games.com\"><img height=\"100\" src=\"doc/ds-logo.png\"></a>\n<a href=\"https://docs.unrealengine.com/4.26/en-US/WhatsNew/Builds/ReleaseNotes/4_25/\"><img height=\"100\" src=\"doc/unreal-logo.svg\"></a>\n<a href=\"https://cab.spbu.ru/software/spades/\"><img height=\"100\" src=\"doc/spades-logo.png\"></a>\n\n\n# Building\n\n## Windows\n\nOpen `ide/vs2022/mimalloc.sln` in Visual Studio 2022 and build.\nThe `mimalloc-lib` project builds a static library (in `out/msvc-x64`), while the\n`mimalloc-override-dll` project builds a DLL for overriding malloc\nin the entire program.\n\n## Linux, macOS, BSD, etc.\n\nWe use [`cmake`](https://cmake.org) as the build system:\n\n```\n> mkdir -p out/release\n> cd out/release\n> cmake ../..\n> make\n```\nThis builds the library as a shared (dynamic)\nlibrary (`.so` or `.dylib`), a static library (`.a`), and\nas a single object file (`.o`).\n\n`> sudo make install` (install the library and header files in `/usr/local/lib`  and `/usr/local/include`)\n\nYou can build the debug version which does many internal checks and\nmaintains detailed statistics as:\n\n```\n> mkdir -p out/debug\n> cd out/debug\n> cmake -DCMAKE_BUILD_TYPE=Debug ../..\n> make\n```\n\nThis will name the shared library as `libmimalloc-debug.so`.\n\nFinally, you can build a _secure_ version that uses guard pages, encrypted free lists, etc., as:\n\n```\n> mkdir -p out/secure\n> cd out/secure\n> cmake -DMI_SECURE=ON ../..\n> make\n```\n\nThis will name the shared library as `libmimalloc-secure.so`.\nUse `cmake ../.. -LH` to see all the available build options.\n\nThe examples use the default compiler. If you like to use another, use:\n\n```\n> CC=clang CXX=clang++ cmake ../..\n```\n\n## Cmake with Visual Studio\n\nYou can also use cmake on Windows. Open a Visual Studio 2022 development prompt \nand invoke `cmake` with the right [generator](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html) \nand architecture, like:\n\n```\n> cmake ..\\.. -G \"Visual Studio 17 2022\" -A x64 -DMI_OVERRIDE=ON\n```\n\nThe cmake build type is specified when actually building, for example:\n\n```\n> cmake --build . --config=Release\n```\n\nYou can also install the [LLVM toolset](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-170#install-1) \non Windows to build with the `clang-cl` compiler directly:\n\n```\n> cmake ../.. -G \"Visual Studio 17 2022\" -T ClangCl\n```\n\n\n## Single Source\n\nYou can also directly build the single `src/static.c` file as part of your project without\nneeding `cmake` at all. Make sure to also add the mimalloc `include` directory to the include path.\n\n\n# Using the Library\n\nThe preferred usage is including `<mimalloc.h>`, linking with\nthe shared- or static library, and using the `mi_malloc` API exclusively for allocation. For example,\n```\n> gcc -o myprogram -lmimalloc myfile.c\n```\n\nmimalloc uses only safe OS calls (`mmap` and `VirtualAlloc`) and can co-exist\nwith other allocators linked to the same program.\nIf you use `cmake`, you can simply use:\n```\nfind_package(mimalloc 1.8 REQUIRED)\n```\nin your `CMakeLists.txt` to find a locally installed mimalloc. Then use either:\n```\ntarget_link_libraries(myapp PUBLIC mimalloc)\n```\nto link with the shared (dynamic) library, or:\n```\ntarget_link_libraries(myapp PUBLIC mimalloc-static)\n```\nto link with the static library. See `test\\CMakeLists.txt` for an example.\n\nFor best performance in C++ programs, it is also recommended to override the\nglobal `new` and `delete` operators. For convenience, mimalloc provides\n[`mimalloc-new-delete.h`](include/mimalloc-new-delete.h) which does this for you -- just include it in a single(!) source file in your project.\nIn C++, mimalloc also provides the `mi_stl_allocator` struct which implements the `std::allocator`\ninterface.\n\nYou can pass environment variables to print verbose messages (`MIMALLOC_VERBOSE=1`)\nand statistics (`MIMALLOC_SHOW_STATS=1`) (in the debug version):\n```\n> env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363\n\n175451865205073170563711388363 = 374456281610909315237213 * 468551\n\nsubproc 0\n blocks          peak       total     current       block      total#\n  bin S    4:    75.3 KiB    55.2 MiB     0          32   B       1.8 M    ok\n  bin S    6:    31.0 KiB   180.4 KiB     0          48   B       3.8 K    ok\n  bin S    8:    64   B      64   B       0          64   B       1        ok\n  bin S    9:   160   B     160   B       0          80   B       2        ok\n  bin S   17:     1.2 KiB     1.2 KiB     0         320   B       4        ok\n  bin S   21:   640   B       3.1 KiB     0         640   B       5        ok\n  bin S   33:     5.0 KiB     5.0 KiB     0           5.0 KiB     1        ok\n\n  binned    :    84.2 Ki     41.5 Mi      0                                ok\n  huge      :     0           0           0                                ok\n  total     :    84.2 KiB    41.5 MiB     0\n  malloc req:                29.7 MiB\n\n pages           peak       total     current       block      total#\n  touched   :   152.8 KiB   152.8 KiB   152.8 KiB\n  pages     :     8          14           0                                ok\n  abandoned :     1         249           0                                ok\n  reclaima  :     0\n  reclaimf  :   249\n  reabandon :     0\n  waits     :     0\n  extended  :    38\n  retire    :    35\n  searches  :     0.7 avg\n\n arenas          peak       total     current       block      total#\n  reserved  :     1.0 GiB     1.0 GiB     1.0 GiB\n  committed :     4.8 MiB     4.8 MiB     4.4 MiB\n  reset     :     0\n  purged    :   385.5 Ki\n  arenas    :     1\n  rollback  :     0\n  mmaps     :     3\n  commits   :     0\n  resets    :     1\n  purges    :     2\n  guarded   :     0\n  heaps     :     1           1           1\n\n process         peak       total     current       block      total#\n  threads   :     1           1           1\n  numa nodes:     1\n  elapsed   :     0.553 s\n  process   : user: 0.557 s, system: 0.013 s, faults: 29, peak rss: 2.1 MiB, peak commit: 4.8 MiB\n```\n\nThe above model of using the `mi_` prefixed API is not always possible\nthough in existing programs that already use the standard malloc interface,\nand another option is to override the standard malloc interface\ncompletely and redirect all calls to the _mimalloc_ library instead .\n\n## Environment Options\n\nYou can set further options either programmatically (using [`mi_option_set`](https://microsoft.github.io/mimalloc/group__options.html)), or via environment variables:\n\n- `MIMALLOC_SHOW_STATS=1`: show statistics when the program terminates.\n- `MIMALLOC_VERBOSE=1`: show verbose messages (including statistics).\n- `MIMALLOC_SHOW_ERRORS=1`: show error and warning messages.\n\nAdvanced options:\n\n- `MIMALLOC_ARENA_EAGER_COMMIT=2`: turns on eager commit for the large arenas (usually 1GiB) from which mimalloc\n   allocates segments and pages. Set this to 2 (default) to\n   only enable this on overcommit systems (e.g. Linux). Set this to 1 to enable explicitly on other systems\n   as well (like Windows or macOS) which may improve performance (as the whole arena is committed at once).\n   Note that eager commit only increases the commit but not the actual the peak resident set\n   (rss) so it is generally ok to enable this.\n- `MIMALLOC_PURGE_DELAY=N`: the delay in `N` milli-seconds (by default `1000` in v3) after which mimalloc will purge\n   OS pages that are not in use. This signals to the OS that the underlying physical memory can be reused which\n   can reduce memory fragmentation especially in long running (server) programs. Setting `N` to `0` purges immediately when\n   a page becomes unused which can improve memory usage but also decreases performance.\n   Setting it to `-1` disables purging completely.\n- `MIMALLOC_PURGE_DECOMMITS=1`: By default \"purging\" memory means unused memory is decommitted (`MEM_DECOMMIT` on Windows,\n   `MADV_DONTNEED` (which decresease rss immediately) on `mmap` systems). Set this to 0 to instead \"reset\" unused\n   memory on a purge (`MEM_RESET` on Windows, generally `MADV_FREE` (which does not decrease rss immediately) on `mmap` systems).\n   Mimalloc generally does not \"free\" OS memory but only \"purges\" OS memory, in other words, it tries to keep virtual\n   address ranges and decommits within those ranges (to make the underlying physical memory available to other processes).\n\nFurther options for large workloads and services:\n\n- `MIMALLOC_ALLOW_THP=1`: By default always allow transparent huge pages (THP) on Linux systems. On Android only this is\n   by default off. When set to `0`, THP is disabled for the process that mimalloc runs in. If enabled, mimalloc also sets\n   the `MIMALLOC_MINIMAL_PURGE_SIZE` in v3 to 2MiB to avoid potentially breaking up transparent huge pages.\n- `MIMALLOC_USE_NUMA_NODES=N`: pretend there are at most `N` NUMA nodes. If not set, the actual NUMA nodes are detected\n   at runtime. Setting `N` to 1 may avoid problems in some virtual environments. Also, setting it to a lower number than\n   the actual NUMA nodes is fine and will only cause threads to potentially allocate more memory across actual NUMA\n   nodes (but this can happen in any case as NUMA local allocation is always a best effort but not guaranteed).\n- `MIMALLOC_ALLOW_LARGE_OS_PAGES=0`: Set to 1 to use large OS pages (2 or 4MiB) when available; for some workloads this can\n   significantly improve performance. However, large OS pages cannot be purged or shared with other processes so may lead\n   to increased memory usage in some cases.\n   Use `MIMALLOC_VERBOSE` to check if the large OS pages are enabled -- usually one needs\n   to explicitly give permissions for large OS pages (as on [Windows][windows-huge] and [Linux][linux-huge]). However, sometimes\n   the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that\n   can have fragmented memory (for that reason, we generally recommend to use `MIMALLOC_RESERVE_HUGE_OS_PAGES` instead whenever possible).\n- `MIMALLOC_RESERVE_HUGE_OS_PAGES=N`: where `N` is the number of 1GiB _huge_ OS pages. This reserves the huge pages at\n   startup and sometimes this can give a large (latency) performance improvement on big workloads.\n   Usually it is better to not use `MIMALLOC_ALLOW_LARGE_OS_PAGES=1` in combination with this setting. Just like large\n   OS pages, use with care as reserving\n   contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at\n   startup only once).\n   Note that we usually need to explicitly give permission for huge OS pages (as on [Windows][windows-huge] and [Linux][linux-huge])).\n   The huge pages are usually allocated evenly among NUMA nodes.\n   We can use `MIMALLOC_RESERVE_HUGE_OS_PAGES_AT=N` where `N` is the numa node (starting at 0) to allocate all\n   the huge pages at a specific numa node instead.\n\nUse caution when using `fork` in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write\nfor all pages in the original process including the huge OS pages. When any memory is now written in that area, the\nOS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in large increments.\n\n[linux-huge]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/tuning_and_optimizing_red_hat_enterprise_linux_for_oracle_9i_and_10g_databases/sect-oracle_9i_and_10g_tuning_guide-large_memory_optimization_big_pages_and_huge_pages-configuring_huge_pages_in_red_hat_enterprise_linux_4_or_5\n[windows-huge]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows?view=sql-server-2017\n\n## Secure Mode\n\n_mimalloc_ can be build in secure mode by using the `-DMI_SECURE=ON` flags in `cmake`. This build enables various mitigations\nto make mimalloc more robust against exploits. In particular:\n\n- All internal mimalloc pages are surrounded by guard pages and the heap metadata is behind a guard page as well (so a buffer overflow\n  exploit cannot reach into the metadata).\n- All free list pointers are\n  [encoded](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396)\n  with per-page keys which is used both to prevent overwrites with a known pointer, as well as to detect heap corruption.\n- Double free's are detected (and ignored).\n- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to\n  mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc\n  from the OS are also address randomized.\n\nAs always, evaluate with care as part of an overall security strategy as all of the above are mitigations but not guarantees.\n\n## Debug Mode\n\nWhen _mimalloc_ is built using debug mode, (`-DCMAKE_BUILD_TYPE=Debug`), \nvarious checks are done at runtime to catch development errors.\n\n- Statistics are maintained in detail for each object size. They can be shown using `MIMALLOC_SHOW_STATS=1` at runtime.\n- All objects have padding at the end to detect (byte precise) heap block overflows.\n- Double free's, and freeing invalid heap pointers are detected.\n- Corrupted free-lists and some forms of use-after-free are detected.\n\n## Guarded Mode\n\n<span id=\"guarded\">_mimalloc_ can be build in guarded mode using the `-DMI_GUARDED=ON` flags in `cmake`.</span>\nThis enables placing OS guard pages behind certain object allocations to catch buffer overflows as they occur.\nThis can be invaluable to catch buffer-overflow bugs in large programs. However, it also means that any object\nallocated with a guard page takes at least 8 KiB memory for the guard page and its alignment. As such, allocating\na guard page for every allocation may be too expensive both in terms of memory, and in terms of performance with\nmany system calls. Therefore, there are various environment variables (and options) to tune this:\n\n- `MIMALLOC_GUARDED_SAMPLE_RATE=N`: Set the sample rate to `N` (by default 4000). This mode places a guard page\n  behind every `N` suitable object allocations (per thread). Since the performance in guarded mode without placing\n  guard pages is close to release mode, this can be used to enable guard pages even in production to catch latent \n  buffer overflow bugs. Set the sample rate to `1` to guard every object, and to `0` to place no guard pages at all.\n\n- `MIMALLOC_GUARDED_SAMPLE_SEED=N`: Start sampling at `N` (by default random). Can be used to reproduce a buffer\n  overflow if needed.\n\n- `MIMALLOC_GUARDED_MIN=N`, `MIMALLOC_GUARDED_MAX=N`: Minimal and maximal _rounded_ object sizes for which a guard \n  page is considered (`0` and `1GiB` respectively). If you suspect a buffer overflow occurs with an object of size\n  141, set the minimum and maximum to `148` and the sample rate to `1` to have all of those guarded.\n\n- `MIMALLOC_GUARDED_PRECISE=1`: If we have an object of size 13, we would usually place it an aligned 16 bytes in\n  front of the guard page. Using `MIMALLOC_GUARDED_PRECISE` places it exactly 13 bytes before a page so that even\n  a 1 byte overflow is detected. This violates the C/C++ minimal alignment guarantees though so use with care.\n\n\n# Overriding Standard Malloc\n\nOverriding the standard `malloc` (and `new`) can be done either _dynamically_ or _statically_.\n\n## Dynamic override\n\nThis is the recommended way to override the standard malloc interface.\n\n### Dynamic Override on Linux, BSD\n\nOn these ELF-based systems we preload the mimalloc shared\nlibrary so all calls to the standard `malloc` interface are\nresolved to the _mimalloc_ library.\n```\n> env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram\n```\n\nYou can set extra environment variables to check that mimalloc is running,\nlike:\n```\n> env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram\n```\nor run with the debug version to get detailed statistics:\n```\n> env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram\n```\n\n### Dynamic Override on MacOS\n\nOn macOS we can also preload the mimalloc shared\nlibrary so all calls to the standard `malloc` interface are\nresolved to the _mimalloc_ library.\n```\n> env DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram\n```\n\nNote that certain security restrictions may apply when doing this from\nthe [shell](https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash).\n\n\n### Dynamic Override on Windows\n\n<span id=\"override_on_windows\">We use a separate redirection DLL to override mimalloc on Windows</span> \nsuch that we redirect all malloc/free calls that go through the (dynamic) C runtime allocator, \nincluding those from other DLL's or libraries. As it intercepts all allocation calls on a low level, \nit can be used on large programs that include other 3rd party components.\nThere are four requirements to make the overriding work well:\n\n1. Use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch).\n\n2. Link your program explicitly with the `mimalloc.dll.lib` export library for the `mimalloc.dll`.\n   (which must be compiled with `-DMI_OVERRIDE=ON`, which is the default though).\n   To ensure the `mimalloc.dll` is actually loaded at run-time it is easiest \n   to insert some call to the mimalloc API in the `main` function, like `mi_version()`\n   (or use the `/include:mi_version` switch on the linker command, or\n   similarly, `#pragma comment(linker, \"/include:mi_version\")` in some source file). \n   See the `mimalloc-test-override` project for an example on how to use this. \n\n3. The `mimalloc-redirect.dll` must be put in the same directory as the main \n   `mimalloc.dll` at runtime (as it is a dependency of that DLL).\n   The redirection DLL ensures that all calls to the C runtime malloc API get \n   redirected to mimalloc functions (which reside in `mimalloc.dll`).\n\n4. Ensure the `mimalloc.dll` comes as early as possible in the import\n   list of the final executable (so it can intercept all potential allocations).\n   You can use `minject -l <exe>` to check this if needed.\n\nFor best performance on Windows with C++, it is also recommended to also override \nthe `new`/`delete` operations (by including [`mimalloc-new-delete.h`](include/mimalloc-new-delete.h)\na single(!) source file in your project).\n\nThe environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic\noverriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully \nredirected.\n\nFor different platforms than x64, you may need a specific [redirection dll](bin).\nFurthermore, we cannot always re-link an executable or ensure `mimalloc.dll` comes\nfirst in the import table. In such cases the [`minject`](bin) tool can be used\nto patch the executable's import tables.\n\n\n## Static override\n\nOn Unix-like systems, you can also statically link with _mimalloc_ to override the standard\nmalloc interface. The recommended way is to link the final program with the\n_mimalloc_ single object file (`mimalloc.o`). We use\nan object file instead of a library file as linkers give preference to\nthat over archives to resolve symbols. To ensure that the standard\nmalloc interface resolves to the _mimalloc_ library, link it as the first\nobject file. For example:\n\n```\n> gcc -o myprogram mimalloc.o  myfile1.c ...\n```\n\nAnother way to override statically that works on all platforms, is to\nlink statically to mimalloc (as shown in the introduction) and include a\nheader file in each source file that re-defines `malloc` etc. to `mi_malloc`.\nThis is provided by [`mimalloc-override.h`](include/mimalloc-override.h). This only works \nreliably though if all sources are\nunder your control or otherwise mixing of pointers from different heaps may occur!\n\n\n# Tools\n\nGenerally, we recommend using the standard allocator with memory tracking tools, but mimalloc\ncan also be build to support the [address sanitizer][asan] or the excellent [Valgrind] tool.\nMoreover, it can be build to support Windows event tracing ([ETW]).\nThis has a small performance overhead but does allow detecting memory leaks and byte-precise\nbuffer overflows directly on final executables. See also the `test/test-wrong.c` file to test with various tools.\n\n## Valgrind\n\nTo build with [valgrind] support, use the `MI_TRACK_VALGRIND=ON` cmake option:\n\n```\n> cmake ../.. -DMI_TRACK_VALGRIND=ON\n```\n\nThis can also be combined with secure mode or debug mode.\nYou can then run your programs directly under valgrind:\n\n```\n> valgrind <myprogram>\n```\n\nIf you rely on overriding `malloc`/`free` by mimalloc (instead of using the `mi_malloc`/`mi_free` API directly),\nyou also need to tell `valgrind` to not intercept those calls itself, and use:\n\n```\n> MIMALLOC_SHOW_STATS=1 valgrind  --soname-synonyms=somalloc=*mimalloc* -- <myprogram>\n```\n\nBy setting the `MIMALLOC_SHOW_STATS` environment variable you can check that mimalloc is indeed\nused and not the standard allocator. Even though the [Valgrind option][valgrind-soname]\nis called `--soname-synonyms`, this also works when overriding with a static library or object file.\nTo dynamically override mimalloc using `LD_PRELOAD` together with `valgrind`, use:\n\n```\n> valgrind --trace-children=yes --soname-synonyms=somalloc=*mimalloc* /usr/bin/env LD_PRELOAD=/usr/lib/libmimalloc.so -- <myprogram>\n```\n\nSee also the `test/test-wrong.c` file to test with `valgrind`.\n\nValgrind support is in its initial development -- please report any issues.\n\n[Valgrind]: https://valgrind.org/\n[valgrind-soname]: https://valgrind.org/docs/manual/manual-core.html#opt.soname-synonyms\n\n## ASAN\n\nTo build with the address sanitizer, use the `-DMI_TRACK_ASAN=ON` cmake option:\n\n```\n> cmake ../.. -DMI_TRACK_ASAN=ON\n```\n\nThis can also be combined with secure mode or debug mode.\nYou can then run your programs as:'\n\n```\n> ASAN_OPTIONS=verbosity=1 <myprogram>\n```\n\nWhen you link a program with an address sanitizer build of mimalloc, you should\ngenerally compile that program too with the address sanitizer enabled.\nFor example, assuming you build mimalloc in `out/debug`:\n\n```\nclang -g -o test-wrong -Iinclude test/test-wrong.c out/debug/libmimalloc-asan-debug.a -lpthread -fsanitize=address -fsanitize-recover=address\n```\n\nSince the address sanitizer redirects the standard allocation functions, on some platforms (macOSX for example)\nit is required to compile mimalloc with `-DMI_OVERRIDE=OFF`.\nAddress sanitizer support is in its initial development -- please report any issues.\n\n[asan]: https://github.com/google/sanitizers/wiki/AddressSanitizer\n\n## ETW\n\nEvent tracing for Windows ([ETW]) provides a high performance way to capture all allocations though\nmimalloc and analyze them later. To build with ETW support, use the `-DMI_TRACK_ETW=ON` cmake option.\n\nYou can then capture an allocation trace using the Windows performance recorder (WPR), using the\n`src/prim/windows/etw-mimalloc.wprp` profile. In an admin prompt, you can use:\n```\n> wpr -start src\\prim\\windows\\etw-mimalloc.wprp -filemode\n> <my_mimalloc_program>\n> wpr -stop <my_mimalloc_program>.etl\n```\nand then open `<my_mimalloc_program>.etl` in the Windows Performance Analyzer (WPA), or\nuse a tool like [TraceControl] that is specialized for analyzing mimalloc traces.\n\n[ETW]: https://learn.microsoft.com/en-us/windows-hardware/test/wpt/event-tracing-for-windows\n[TraceControl]: https://github.com/xinglonghe/TraceControl\n\n\n# Performance\n\nLast update: 2021-01-30\n\nWe tested _mimalloc_ against many other top allocators over a wide\nrange of benchmarks, ranging from various real world programs to\nsynthetic benchmarks that see how the allocator behaves under more\nextreme circumstances. In our benchmark suite, _mimalloc_ outperforms other leading\nallocators (_jemalloc_, _tcmalloc_, _Hoard_, etc), and has a similar memory footprint. A nice property is that it\ndoes consistently well over the wide range of benchmarks.\n\nGeneral memory allocators are interesting as there exists no algorithm that is\noptimal -- for a given allocator one can usually construct a workload\nwhere it does not do so well. The goal is thus to find an allocation\nstrategy that performs well over a wide range of benchmarks without\nsuffering from (too much) underperformance in less common situations.\n\nAs always, interpret these results with care since some benchmarks test synthetic\nor uncommon situations that may never apply to your workloads. For example, most\nallocators do not do well on `xmalloc-testN` but that includes even the best\nindustrial allocators like _jemalloc_ and _tcmalloc_ that are used in some of\nthe world's largest systems (like Chrome or FreeBSD).\n\nAlso, the benchmarks here do not measure the behaviour on very large and long-running server workloads,\nor worst-case latencies of allocation. Much work has gone into `mimalloc` to work well on such\nworkloads (for example, to reduce virtual memory fragmentation on long-running services)\nbut such optimizations are not always reflected in the current benchmark suite.\n\nWe show here only an overview -- for\nmore specific details and further benchmarks we refer to the\n[technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action).\nThe benchmark suite is automated and available separately\nas [mimalloc-bench](https://github.com/daanx/mimalloc-bench).\n\n\n## Benchmark Results on a 16-core AMD 5950x (Zen3)\n\nTesting on the 16-core AMD 5950x processor at 3.4Ghz (4.9Ghz boost), with\n32GiB memory at 3600Mhz, running\tUbuntu 20.04 with glibc 2.31 and GCC 9.3.0.\n\nWe measure three versions of _mimalloc_: the main version `mi` (tag:v1.7.0),\nthe new v2.0 beta version as `xmi` (tag:v2.0.0), and the main version in secure mode as `smi` (tag:v1.7.0).\n\nThe other allocators are\nGoogle's [_tcmalloc_](https://github.com/gperftools/gperftools) (`tc`, tag:gperftools-2.8.1) used in Chrome,\nFacebook's [_jemalloc_](https://github.com/jemalloc/jemalloc) (`je`, tag:5.2.1) by Jason Evans used in Firefox and FreeBSD,\nthe Intel thread building blocks [allocator](https://github.com/intel/tbb) (`tbb`, tag:v2020.3),\n[rpmalloc](https://github.com/mjansson/rpmalloc) (`rp`,tag:1.4.1) by Mattias Jansson,\nthe original scalable [_Hoard_](https://github.com/emeryberger/Hoard) (git:d880f72) allocator by Emery Berger \\[1],\nthe memory compacting [_Mesh_](https://github.com/plasma-umass/Mesh) (git:67ff31a) allocator by\nBobby Powers _et al_ \\[8],\nand finally the default system allocator (`glibc`, 2.31) (based on _PtMalloc2_).\n\n<img width=\"90%\" src=\"doc/bench-2021/bench-amd5950x-2021-01-30-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2021/bench-amd5950x-2021-01-30-b.svg\"/>\n\nAny benchmarks ending in `N` run on all 32 logical cores in parallel.\nResults are averaged over 10 runs and reported relative\nto mimalloc (where 1.2 means it took 1.2&times; longer to run).\nThe legend also contains the _overall relative score_ between the\nallocators where 100 points is the maximum if an allocator is fastest on\nall benchmarks.\n\nThe single threaded _cfrac_ benchmark by Dave Barrett is an implementation of\ncontinued fraction factorization which uses many small short-lived allocations.\nAll allocators do well on such common usage, where _mimalloc_ is just a tad\nfaster than _tcmalloc_ and\n_jemalloc_.\n\nThe _leanN_ program is interesting as a large realistic and\nconcurrent workload of the [Lean](https://github.com/leanprover/lean)\ntheorem prover compiling its own standard library, and there is a 13%\nspeedup over _tcmalloc_. This is\nquite significant: if Lean spends 20% of its time in the\nallocator that means that _mimalloc_ is 1.6&times; faster than _tcmalloc_\nhere. (This is surprising as that is not measured in a pure\nallocation benchmark like _alloc-test_. We conjecture that we see this\noutsized improvement here because _mimalloc_ has better locality in\nthe allocation which improves performance for the *other* computations\nin a program as well).\n\nThe single threaded _redis_ benchmark again show that most allocators do well on such workloads.\n\nThe _larsonN_ server benchmark by Larson and Krishnan \\[2] allocates and frees between threads. They observed this\nbehavior (which they call _bleeding_) in actual server applications, and the benchmark simulates this.\nHere, _mimalloc_ is quite a bit faster than _tcmalloc_ and _jemalloc_ probably due to the object migration between different threads.\n\nThe _mstressN_ workload performs many allocations and re-allocations,\nand migrates objects between threads (as in _larsonN_). However, it also\ncreates  and destroys the _N_ worker threads a few times keeping some objects\nalive beyond the life time of the allocating thread. We observed this\nbehavior in many larger server applications.\n\nThe [_rptestN_](https://github.com/mjansson/rpmalloc-benchmark) benchmark\nby Mattias Jansson is a allocator test originally designed\nfor _rpmalloc_, and tries to simulate realistic allocation patterns over\nmultiple threads. Here the differences between allocators become more apparent.\n\nThe second benchmark set tests specific aspects of the allocators and\nshows even more extreme differences between them.\n\nThe _alloc-test_, by\n[OLogN Technologies AG](http://ithare.com/testing-memory-allocators-ptmalloc2-tcmalloc-hoard-jemalloc-while-trying-to-simulate-real-world-loads/), is a very allocation intensive benchmark doing millions of\nallocations in various size classes. The test is scaled such that when an\nallocator performs almost identically on _alloc-test1_ as _alloc-testN_ it\nmeans that it scales linearly.\n\nThe _sh6bench_ and _sh8bench_ benchmarks are\ndeveloped by [MicroQuill](http://www.microquill.com/) as part of SmartHeap.\nIn _sh6bench_ _mimalloc_ does much\nbetter than the others (more than 2.5&times; faster than _jemalloc_).\nWe cannot explain this well but believe it is\ncaused in part by the \"reverse\" free-ing pattern in _sh6bench_.\nThe _sh8bench_ is a variation with object migration\nbetween threads; whereas _tcmalloc_ did well on _sh6bench_, the addition of object migration causes it to be 10&times; slower than before.\n\nThe _xmalloc-testN_ benchmark by Lever and Boreham \\[5] and Christian Eder, simulates an asymmetric workload where\nsome threads only allocate, and others only free -- they observed this pattern in\nlarger server applications. Here we see that\nthe _mimalloc_ technique of having non-contended sharded thread free\nlists pays off as it outperforms others by a very large margin. Only _rpmalloc_, _tbb_, and _glibc_ also scale well on this benchmark.\n\nThe _cache-scratch_ benchmark by Emery Berger \\[1], and introduced with\nthe Hoard allocator to test for _passive-false_ sharing of cache lines.\nWith a single thread they all\nperform the same, but when running with multiple threads the potential allocator\ninduced false sharing of the cache lines can cause large run-time differences.\nCrundal \\[6] describes in detail why the false cache line sharing occurs in the _tcmalloc_ design, and also discusses how this\ncan be avoided with some small implementation changes.\nOnly the _tbb_, _rpmalloc_ and _mesh_ allocators also avoid the\ncache line sharing completely, while _Hoard_ and _glibc_ seem to mitigate\nthe effects. Kukanov and Voss \\[7] describe in detail\nhow the design of _tbb_ avoids the false cache line sharing.\n\n\n## On a 36-core Intel Xeon\n\nFor completeness, here are the results on a big Amazon\n[c5.18xlarge](https://aws.amazon.com/ec2/instance-types/#Compute_Optimized) instance\nconsisting of a 2&times;18-core Intel Xeon (Cascade Lake) at 3.4GHz (boost 3.5GHz)\nwith 144GiB ECC memory, running\tUbuntu 20.04 with glibc 2.31, GCC 9.3.0, and\nClang 10.0.0. This time, the mimalloc allocators (mi, xmi, and smi) were\ncompiled with the Clang compiler instead of GCC.\nThe results are similar to the AMD results but it is interesting to\nsee the differences in the _larsonN_, _mstressN_, and _xmalloc-testN_ benchmarks.\n\n<img width=\"90%\" src=\"doc/bench-2021/bench-c5-18xlarge-2021-01-30-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2021/bench-c5-18xlarge-2021-01-30-b.svg\"/>\n\n\n## Peak Working Set\n\nThe following figure shows the peak working set (rss) of the allocators\non the benchmarks (on the c5.18xlarge instance).\n\n<img width=\"90%\" src=\"doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-b.svg\"/>\n\nNote that the _xmalloc-testN_ memory usage should be disregarded as it\nallocates more the faster the program runs. Similarly, memory usage of\n_larsonN_, _mstressN_, _rptestN_ and _sh8bench_ can vary depending on scheduling and\nspeed. Nevertheless, we hope to improve the memory usage on _mstressN_\nand _rptestN_ (just as _cfrac_, _larsonN_ and _sh8bench_ have a small working set which skews the results).\n\n<!--\n# Previous Benchmarks\n\nTodo: should we create a separate page for this?\n\n## Benchmark Results on 36-core Intel: 2020-01-20\n\nTesting on a big Amazon EC2 compute instance\n([c5.18xlarge](https://aws.amazon.com/ec2/instance-types/#Compute_Optimized))\nconsisting of a 72 processor Intel Xeon at 3GHz\nwith 144GiB ECC memory, running\tUbuntu 18.04.1 with glibc 2.27 and GCC 7.4.0.\nThe measured allocators are _mimalloc_ (xmi, tag:v1.4.0, page reset enabled)\nand its secure build as _smi_,\nGoogle's [_tcmalloc_](https://github.com/gperftools/gperftools) (tc, tag:gperftools-2.7) used in Chrome,\nFacebook's [_jemalloc_](https://github.com/jemalloc/jemalloc) (je, tag:5.2.1) by Jason Evans used in Firefox and FreeBSD,\nthe Intel thread building blocks [allocator](https://github.com/intel/tbb) (tbb, tag:2020),\n[rpmalloc](https://github.com/mjansson/rpmalloc) (rp,tag:1.4.0) by Mattias Jansson,\nthe original scalable [_Hoard_](https://github.com/emeryberger/Hoard) (tag:3.13) allocator by Emery Berger \\[1],\nthe memory compacting [_Mesh_](https://github.com/plasma-umass/Mesh) (git:51222e7) allocator by\nBobby Powers _et al_ \\[8],\nand finally the default system allocator (glibc, 2.27) (based on _PtMalloc2_).\n\n<img width=\"90%\" src=\"doc/bench-2020/bench-c5-18xlarge-2020-01-20-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2020/bench-c5-18xlarge-2020-01-20-b.svg\"/>\n\nThe following figure shows the peak working set (rss) of the allocators\non the benchmarks (on the c5.18xlarge instance).\n\n<img width=\"90%\" src=\"doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-b.svg\"/>\n\n\n## On 24-core AMD Epyc, 2020-01-16\n\nFor completeness, here are the results on a\n[r5a.12xlarge](https://aws.amazon.com/ec2/instance-types/#Memory_Optimized) instance\nhaving a 48 processor AMD Epyc 7000 at 2.5GHz with 384GiB of memory.\nThe results are similar to the Intel results but it is interesting to\nsee the differences in the _larsonN_, _mstressN_, and _xmalloc-testN_ benchmarks.\n\n<img width=\"90%\" src=\"doc/bench-2020/bench-r5a-12xlarge-2020-01-16-a.svg\"/>\n<img width=\"90%\" src=\"doc/bench-2020/bench-r5a-12xlarge-2020-01-16-b.svg\"/>\n\n-->\n\n\n# References\n\n- \\[1] Emery D. Berger, Kathryn S. McKinley, Robert D. Blumofe, and Paul R. Wilson.\n   _Hoard: A Scalable Memory Allocator for Multithreaded Applications_\n   the Ninth International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS-IX). Cambridge, MA, November 2000.\n   [pdf](http://www.cs.utexas.edu/users/mckinley/papers/asplos-2000.pdf)\n\n- \\[2] P. Larson and M. Krishnan. _Memory allocation for long-running server applications_.\n  In ISMM, Vancouver, B.C., Canada, 1998. [pdf](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.45.1947&rep=rep1&type=pdf)\n\n- \\[3] D. Grunwald, B. Zorn, and R. Henderson.\n  _Improving the cache locality of memory allocation_. In R. Cartwright, editor,\n  Proceedings of the Conference on Programming Language Design and Implementation, pages 177–186, New York, NY, USA, June 1993. [pdf](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.43.6621&rep=rep1&type=pdf)\n\n- \\[4] J. Barnes and P. Hut. _A hierarchical O(n*log(n)) force-calculation algorithm_. Nature, 324:446-449, 1986.\n\n- \\[5] C. Lever, and D. Boreham. _Malloc() Performance in a Multithreaded Linux Environment._\n  In USENIX Annual Technical Conference, Freenix Session. San Diego, CA. Jun. 2000.\n  Available at <https://github.com/kuszmaul/SuperMalloc/tree/master/tests>\n\n- \\[6] Timothy Crundal. _Reducing Active-False Sharing in TCMalloc_. 2016. CS16S1 project at the Australian National University. [pdf](http://courses.cecs.anu.edu.au/courses/CSPROJECTS/16S1/Reports/Timothy_Crundal_Report.pdf)\n\n- \\[7] Alexey Kukanov, and Michael J Voss.\n   _The Foundations for Scalable Multi-Core Software in Intel Threading Building Blocks._\n   Intel Technology Journal 11 (4). 2007\n\n- \\[8] Bobby Powers, David Tench, Emery D. Berger, and Andrew McGregor.\n _Mesh: Compacting Memory Management for C/C++_\n In Proceedings of the 40th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI'19), June 2019, pages 333-–346.\n\n<!--\n- \\[9] Paul Liétar, Theodore Butler, Sylvan Clebsch, Sophia Drossopoulou, Juliana Franco, Matthew J Parkinson,\n  Alex Shamis, Christoph M Wintersteiger, and David Chisnall.\n  _Snmalloc: A Message Passing Allocator._\n  In Proceedings of the 2019 ACM SIGPLAN International Symposium on Memory Management, 122–135. ACM. 2019.\n-->\n\n# Contributing\n\nThis project welcomes contributions and suggestions.  Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit https://cla.microsoft.com.\n\nWhen you submit a pull request, a CLA-bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n\n\n# Older Release Notes\n\n* 2024-05-21, `v1.8.7`, `v2.1.7`: Fix build issues on less common platforms. Started upstreaming patches\n  from the CPython [integration](https://github.com/python/cpython/issues/113141#issuecomment-2119255217). Upstream `vcpkg` patches.\n* 2024-05-13, `v1.8.6`, `v2.1.6`: Fix build errors on various (older) platforms. Refactored aligned allocation.\n* 2024-04-22, `v1.8.4`, `v2.1.4`: Fixes various bugs and build issues. Add `MI_LIBC_MUSL` cmake flag for musl builds.\n  Free-ing code is refactored into a separate module (`free.c`). Mimalloc page info is simplified with the block size\n  directly available (and new `block_size_shift` to improve aligned block free-ing).\n  New approach to collection of abandoned segments: When\n  a thread terminates the segments it owns are abandoned (containing still live objects) and these can be\n  reclaimed by other threads. We no longer use a list of abandoned segments but this is now done using bitmaps in arena's\n  which is more concurrent (and more aggressive). Abandoned memory can now also be reclaimed if a thread frees an object in\n  an abandoned page (which can be disabled using `mi_option_abandoned_reclaim_on_free`). The option `mi_option_max_segment_reclaim`\n  gives a maximum percentage of abandoned segments that can be reclaimed per try (=10%).\n\n* 2023-04-24, `v1.8.2`, `v2.1.2`: Fixes build issues on freeBSD, musl, and C17 (UE 5.1.1). Reduce code size/complexity\n  by removing regions and segment-cache's and only use arenas with improved memory purging -- this may improve memory\n  usage as well for larger services. Renamed options for consistency. Improved Valgrind and ASAN checking.\n\n* 2023-04-03, `v1.8.1`, `v2.1.1`: Fixes build issues on some platforms.\n\n* 2023-03-29, `v1.8.0`, `v2.1.0`: Improved support dynamic overriding on Windows 11. Improved tracing precision\n  with [asan](#asan) and [Valgrind](#valgrind), and added Windows event tracing [ETW](#ETW) (contributed by Xinglong He). Created an OS\n  abstraction layer to make it easier to port and separate platform dependent code (in `src/prim`). Fixed C++ STL compilation on older Microsoft C++ compilers, and various small bug fixes.\n\n* 2022-12-23, `v1.7.9`, `v2.0.9`: Supports building with [asan](#asan) and improved [Valgrind](#valgrind) support.\n  Support arbitrary large alignments (in particular for `std::pmr` pools).\n  Added C++ STL allocators attached to a specific heap (thanks @vmarkovtsev).\n  Heap walks now visit all object (including huge objects). Support Windows nano server containers (by Johannes Schindelin,@dscho).\n  Various small bug fixes.\n\n* 2022-11-03, `v1.7.7`, `v2.0.7`: Initial support for [Valgrind](#valgrind) for leak testing and heap block overflow\n  detection. Initial\n  support for attaching heaps to a specific memory area (only in v2). Fix `realloc` behavior for zero size blocks, remove restriction to integral multiple of the alignment in `alloc_align`, improved aligned allocation performance, reduced contention with many threads on few processors (thank you @dposluns!), vs2022 support, support `pkg-config`, .\n\n* 2022-04-14, `v1.7.6`, `v2.0.6`: fix fallback path for aligned OS allocation on Windows, improve Windows aligned allocation\n  even when compiling with older SDK's, fix dynamic overriding on macOS Monterey, fix MSVC C++ dynamic overriding, fix\n  warnings under Clang 14, improve performance if many OS threads are created and destroyed, fix statistics for large object\n  allocations, using MIMALLOC_VERBOSE=1 has no maximum on the number of error messages, various small fixes.\n\n* 2022-02-14, `v1.7.5`, `v2.0.5` (alpha): fix malloc override on\n  Windows 11, fix compilation with musl, potentially reduced\n  committed memory, add `bin/minject` for Windows,\n  improved wasm support, faster aligned allocation,\n  various small fixes.\n\n* 2021-11-14, `v1.7.3`, `v2.0.3` (beta): improved WASM support, improved macOS support and performance (including\n  M1), improved performance for v2 for large objects, Python integration improvements, more standard\n  installation directories, various small fixes.\n* 2021-06-17, `v1.7.2`, `v2.0.2` (beta): support M1, better installation layout on Linux, fix\n  thread_id on Android, prefer 2-6TiB area for aligned allocation to work better on pre-windows 8, various small fixes.\n* 2021-04-06, `v1.7.1`, `v2.0.1` (beta): fix bug in arena allocation for huge pages, improved aslr on large allocations, initial M1 support (still experimental).\n* 2021-01-31, `v2.0.0`: beta release 2.0: new slice algorithm for managing internal mimalloc pages.\n* 2021-01-31, `v1.7.0`: stable release 1.7: support explicit user provided memory regions, more precise statistics,\n  improve macOS overriding, initial support for Apple M1, improved DragonFly support, faster memcpy on Windows, various small fixes.\n\n* 2020-09-24, `v1.6.7`: stable release 1.6: using standard C atomics, passing tsan testing, improved\n  handling of failing to commit on Windows, add [`mi_process_info`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc.h#L156) api call.\n* 2020-08-06, `v1.6.4`: stable release 1.6: improved error recovery in low-memory situations,\n  support for IllumOS and Haiku, NUMA support for Vista/XP, improved NUMA detection for AMD Ryzen, ubsan support.\n* 2020-05-05, `v1.6.3`: stable release 1.6: improved behavior in out-of-memory situations, improved malloc zones on macOS,\n  build PIC static libraries by default, add option to abort on out-of-memory, line buffered statistics.\n* 2020-04-20, `v1.6.2`: stable release 1.6: fix compilation on Android, MingW, Raspberry, and Conda,\n  stability fix for Windows 7, fix multiple mimalloc instances in one executable, fix `strnlen` overload,\n  fix aligned debug padding.\n* 2020-02-17, `v1.6.1`: stable release 1.6: minor updates (build with clang-cl, fix alignment issue for small objects).\n* 2020-02-09, `v1.6.0`: stable release 1.6: fixed potential memory leak, improved overriding\n  and thread local support on FreeBSD, NetBSD, DragonFly, and macOSX. New byte-precise\n  heap block overflow detection in debug mode (besides the double-free detection and free-list\n  corruption detection). Add `nodiscard` attribute to most allocation functions.\n  Enable `MIMALLOC_PAGE_RESET` by default. New reclamation strategy for abandoned heap pages\n  for better memory footprint.\n* 2020-02-09, `v1.5.0`: stable release 1.5: improved free performance, small bug fixes.\n* 2020-01-22, `v1.4.0`: stable release 1.4: improved performance for delayed OS page reset,\nmore eager concurrent free, addition of STL allocator, fixed potential memory leak.\n* 2020-01-15, `v1.3.0`: stable release 1.3: bug fixes, improved randomness and [stronger\nfree list encoding](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) in secure mode.\n\n* 2019-12-22, `v1.2.2`: stable release 1.2: minor updates.\n* 2019-11-22, `v1.2.0`: stable release 1.2: bug fixes, improved secure mode (free list corruption checks, double free mitigation). Improved dynamic overriding on Windows.\n* 2019-10-07, `v1.1.0`: stable release 1.1.\n* 2019-09-01, `v1.0.8`: pre-release 8: more robust windows dynamic overriding, initial huge page support.\n* 2019-08-10, `v1.0.6`: pre-release 6: various performance improvements.\n"
  },
  {
    "path": "src/alloc-aligned.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"  // mi_prim_get_default_heap\n\n#include <string.h>     // memset\n\n// ------------------------------------------------------\n// Aligned Allocation\n// ------------------------------------------------------\n\nstatic bool mi_malloc_is_naturally_aligned( size_t size, size_t alignment ) {\n  // objects up to `MI_MAX_ALIGN_GUARANTEE` are allocated aligned to their size (see `segment.c:_mi_segment_page_start`).\n  mi_assert_internal(_mi_is_power_of_two(alignment) && (alignment > 0));\n  if (alignment > size) return false;\n  if (alignment <= MI_MAX_ALIGN_SIZE) return true;\n  const size_t bsize = mi_good_size(size);\n  return (bsize <= MI_MAX_ALIGN_GUARANTEE && (bsize & (alignment-1)) == 0);\n}\n\n#if MI_GUARDED\nstatic mi_decl_restrict void* mi_heap_malloc_guarded_aligned(mi_heap_t* heap, size_t size, size_t alignment, bool zero) mi_attr_noexcept {\n  // use over allocation for guarded blocksl\n  mi_assert_internal(alignment > 0 && alignment < MI_BLOCK_ALIGNMENT_MAX);\n  const size_t oversize = size + alignment - 1;\n  void* base = _mi_heap_malloc_guarded(heap, oversize, zero);\n  void* p = mi_align_up_ptr(base, alignment);\n  mi_track_align(base, p, (uint8_t*)p - (uint8_t*)base, size);\n  mi_assert_internal(mi_usable_size(p) >= size);\n  mi_assert_internal(_mi_is_aligned(p, alignment));\n  return p;\n}\n\nstatic void* mi_heap_malloc_zero_no_guarded(mi_heap_t* heap, size_t size, bool zero, size_t* usable) {\n  const size_t rate = heap->guarded_sample_rate;\n  // only write if `rate!=0` so we don't write to the constant `_mi_heap_empty`\n  if (rate != 0) { heap->guarded_sample_rate = 0; }\n  void* p = _mi_heap_malloc_zero_ex(heap, size, zero, 0, usable);\n  if (rate != 0) { heap->guarded_sample_rate = rate; }\n  return p;\n}\n#else\nstatic void* mi_heap_malloc_zero_no_guarded(mi_heap_t* heap, size_t size, bool zero, size_t* usable) {\n  return _mi_heap_malloc_zero_ex(heap, size, zero, 0, usable);\n}\n#endif\n\n// Fallback aligned allocation that over-allocates -- split out for better codegen\nstatic mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_overalloc(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero, size_t* usable) mi_attr_noexcept\n{\n  mi_assert_internal(size <= (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE));\n  mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment));\n\n  void* p;\n  size_t oversize;\n  if mi_unlikely(alignment > MI_BLOCK_ALIGNMENT_MAX) {\n    // use OS allocation for very large alignment and allocate inside a huge page (dedicated segment with 1 page)\n    // This can support alignments >= MI_SEGMENT_SIZE by ensuring the object can be aligned at a point in the\n    // first (and single) page such that the segment info is `MI_SEGMENT_SIZE` bytes before it (so it can be found by aligning the pointer down)\n    if mi_unlikely(offset != 0) {\n      // todo: cannot support offset alignment for very large alignments yet\n#if MI_DEBUG > 0\n      _mi_error_message(EOVERFLOW, \"aligned allocation with a very large alignment cannot be used with an alignment offset (size %zu, alignment %zu, offset %zu)\\n\", size, alignment, offset);\n#endif\n      return NULL;\n    }\n    oversize = (size <= MI_SMALL_SIZE_MAX ? MI_SMALL_SIZE_MAX + 1 /* ensure we use generic malloc path */ : size);\n    // note: no guarded as alignment > 0\n    p = _mi_heap_malloc_zero_ex(heap, oversize, false, alignment, usable); // the page block size should be large enough to align in the single huge page block\n    // zero afterwards as only the area from the aligned_p may be committed!\n    if (p == NULL) return NULL;\n  }\n  else {\n    // otherwise over-allocate\n    oversize = (size < MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : size) + alignment - 1;  // adjust for size <= 16; with size 0 and aligment 64k, we would allocate a 64k block and pointing just beyond that.\n    p = mi_heap_malloc_zero_no_guarded(heap, oversize, zero, usable);\n    if (p == NULL) return NULL;\n  }\n  mi_page_t* page = _mi_ptr_page(p);\n\n  // .. and align within the allocation\n  const uintptr_t align_mask = alignment - 1;  // for any x, `(x & align_mask) == (x % alignment)`\n  const uintptr_t poffset = ((uintptr_t)p + offset) & align_mask;\n  const uintptr_t adjust  = (poffset == 0 ? 0 : alignment - poffset);\n  mi_assert_internal(adjust < alignment);\n  void* aligned_p = (void*)((uintptr_t)p + adjust);\n  if (aligned_p != p) {\n    mi_page_set_has_aligned(page, true);\n    #if MI_GUARDED\n    // set tag to aligned so mi_usable_size works with guard pages\n    if (adjust >= sizeof(mi_block_t)) {\n      mi_block_t* const block = (mi_block_t*)p;\n      block->next = MI_BLOCK_TAG_ALIGNED;\n    }\n    #endif\n    _mi_padding_shrink(page, (mi_block_t*)p, adjust + size);\n  }\n  // todo: expand padding if overallocated ?\n\n  mi_assert_internal(mi_page_usable_block_size(page) >= adjust + size);\n  mi_assert_internal(((uintptr_t)aligned_p + offset) % alignment == 0);\n  mi_assert_internal(mi_usable_size(aligned_p)>=size);\n  mi_assert_internal(mi_usable_size(p) == mi_usable_size(aligned_p)+adjust);\n  #if MI_DEBUG > 1\n  mi_page_t* const apage = _mi_ptr_page(aligned_p);\n  void* unalign_p = _mi_page_ptr_unalign(apage, aligned_p);\n  mi_assert_internal(p == unalign_p);\n  #endif\n\n  // now zero the block if needed\n  if (alignment > MI_BLOCK_ALIGNMENT_MAX) {\n    // for the tracker, on huge aligned allocations only the memory from the start of the large block is defined\n    mi_track_mem_undefined(aligned_p, size);\n    if (zero) {\n      _mi_memzero_aligned(aligned_p, mi_usable_size(aligned_p));\n    }\n  }\n\n  if (p != aligned_p) {\n    mi_track_align(p,aligned_p,adjust,mi_usable_size(aligned_p));\n    #if MI_GUARDED\n    mi_track_mem_defined(p, sizeof(mi_block_t));\n    #endif\n  }\n  return aligned_p;\n}\n\n// Generic primitive aligned allocation -- split out for better codegen\nstatic mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_generic(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero, size_t* usable) mi_attr_noexcept\n{\n  mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment));\n  // we don't allocate more than MI_MAX_ALLOC_SIZE (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)\n  if mi_unlikely(size > (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE)) {\n    #if MI_DEBUG > 0\n    _mi_error_message(EOVERFLOW, \"aligned allocation request is too large (size %zu, alignment %zu)\\n\", size, alignment);\n    #endif\n    return NULL;\n  }\n\n  // use regular allocation if it is guaranteed to fit the alignment constraints.\n  // this is important to try as the fast path in `mi_heap_malloc_zero_aligned` only works when there exist\n  // a page with the right block size, and if we always use the over-alloc fallback that would never happen.\n  if (offset == 0 && mi_malloc_is_naturally_aligned(size,alignment)) {\n    void* p = mi_heap_malloc_zero_no_guarded(heap, size, zero, usable);\n    mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0);\n    const bool is_aligned_or_null = (((uintptr_t)p) & (alignment-1))==0;\n    if mi_likely(is_aligned_or_null) {\n      return p;\n    }\n    else {\n      // this should never happen if the `mi_malloc_is_naturally_aligned` check is correct..\n      mi_assert(false);\n      mi_free(p);\n    }\n  }\n\n  // fall back to over-allocation\n  return mi_heap_malloc_zero_aligned_at_overalloc(heap,size,alignment,offset,zero,usable);\n}\n\n\n// Primitive aligned allocation\nstatic void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t size, \n                                            const size_t alignment, const size_t offset, const bool zero,\n                                            size_t* usable) mi_attr_noexcept\n{\n  // note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size.\n  if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) { // require power-of-two (see <https://en.cppreference.com/w/c/memory/aligned_alloc>)\n    #if MI_DEBUG > 0\n    _mi_error_message(EOVERFLOW, \"aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu)\\n\", size, alignment);\n    #endif\n    return NULL;\n  }\n\n  #if MI_GUARDED\n  if (offset==0 && alignment < MI_BLOCK_ALIGNMENT_MAX && mi_heap_malloc_use_guarded(heap,size)) {\n    return mi_heap_malloc_guarded_aligned(heap, size, alignment, zero);\n  }\n  #endif\n\n  // try first if there happens to be a small block available with just the right alignment\n  if mi_likely(size <= MI_SMALL_SIZE_MAX && alignment <= size) {\n    const uintptr_t align_mask = alignment-1;       // for any x, `(x & align_mask) == (x % alignment)`\n    const size_t padsize = size + MI_PADDING_SIZE;\n    mi_page_t* page = _mi_heap_get_free_small_page(heap, padsize);\n    if mi_likely(page->free != NULL) {\n      const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0;\n      if mi_likely(is_aligned)\n      {\n        if (usable!=NULL) { *usable = mi_page_usable_block_size(page); }\n        void* p = (zero ? _mi_page_malloc_zeroed(heap,page,padsize) : _mi_page_malloc(heap,page,padsize)); // call specific page malloc for better codegen\n        mi_assert_internal(p != NULL);\n        mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);\n        mi_track_malloc(p,size,zero);\n        return p;\n      }\n    }\n  }\n\n  // fallback to generic aligned allocation\n  return mi_heap_malloc_zero_aligned_at_generic(heap, size, alignment, offset, zero, usable);\n}\n\n\n// ------------------------------------------------------\n// Optimized mi_heap_malloc_aligned / mi_malloc_aligned\n// ------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, false, NULL);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_malloc_aligned_at(heap, size, alignment, 0);\n}\n\n// ensure a definition is emitted\n#if defined(__cplusplus)\nvoid* _mi_extern_heap_malloc_aligned = (void*)&mi_heap_malloc_aligned;\n#endif\n\n// ------------------------------------------------------\n// Aligned Allocation\n// ------------------------------------------------------\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, true, NULL);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_zalloc_aligned_at(heap, size, alignment, 0);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count, size, &total)) return NULL;\n  return mi_heap_zalloc_aligned_at(heap, total, alignment, offset);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_calloc_aligned_at(heap,count,size,alignment,0);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_malloc_aligned_at(mi_prim_get_default_heap(), size, alignment, offset);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_malloc_aligned(mi_prim_get_default_heap(), size, alignment);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept {\n  return mi_heap_malloc_zero_aligned_at(mi_prim_get_default_heap(), size, alignment, 0, false, block_size);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_zalloc_aligned_at(mi_prim_get_default_heap(), size, alignment, offset);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_zalloc_aligned(mi_prim_get_default_heap(), size, alignment);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept {\n  return mi_heap_malloc_zero_aligned_at(mi_prim_get_default_heap(), size, alignment, 0, true, block_size);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_calloc_aligned_at(mi_prim_get_default_heap(), count, size, alignment, offset);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_calloc_aligned(mi_prim_get_default_heap(), count, size, alignment);\n}\n\n\n// ------------------------------------------------------\n// Aligned re-allocation\n// ------------------------------------------------------\n\nstatic void* mi_heap_realloc_zero_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset, bool zero) mi_attr_noexcept {\n  mi_assert(alignment > 0);\n  if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero,NULL,NULL);\n  if (p == NULL) return mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,zero,NULL);\n  size_t size = mi_usable_size(p);\n  if (newsize <= size && newsize >= (size - (size / 2))\n      && (((uintptr_t)p + offset) % alignment) == 0) {\n    return p;  // reallocation still fits, is aligned and not more than 50% waste\n  }\n  else {\n    // note: we don't zero allocate upfront so we only zero initialize the expanded part\n    void* newp = mi_heap_malloc_aligned_at(heap,newsize,alignment,offset);\n    if (newp != NULL) {\n      if (zero && newsize > size) {\n        // also set last word in the previous allocation to zero to ensure any padding is zero-initialized\n        size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0);\n        _mi_memzero((uint8_t*)newp + start, newsize - start);\n      }\n      _mi_memcpy_aligned(newp, p, (newsize > size ? size : newsize));\n      mi_free(p); // only free if successful\n    }\n    return newp;\n  }\n}\n\nstatic void* mi_heap_realloc_zero_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, bool zero) mi_attr_noexcept {\n  mi_assert(alignment > 0);\n  if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero,NULL,NULL);\n  size_t offset = ((uintptr_t)p % alignment); // use offset of previous allocation (p can be NULL)\n  return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,zero);\n}\n\nmi_decl_nodiscard void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,false);\n}\n\nmi_decl_nodiscard void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept {\n  return mi_heap_realloc_zero_aligned(heap,p,newsize,alignment,false);\n}\n\nmi_decl_nodiscard void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_realloc_zero_aligned_at(heap, p, newsize, alignment, offset, true);\n}\n\nmi_decl_nodiscard void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept {\n  return mi_heap_realloc_zero_aligned(heap, p, newsize, alignment, true);\n}\n\nmi_decl_nodiscard void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(newcount, size, &total)) return NULL;\n  return mi_heap_rezalloc_aligned_at(heap, p, total, alignment, offset);\n}\n\nmi_decl_nodiscard void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(newcount, size, &total)) return NULL;\n  return mi_heap_rezalloc_aligned(heap, p, total, alignment);\n}\n\nmi_decl_nodiscard void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_realloc_aligned_at(mi_prim_get_default_heap(), p, newsize, alignment, offset);\n}\n\nmi_decl_nodiscard void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept {\n  return mi_heap_realloc_aligned(mi_prim_get_default_heap(), p, newsize, alignment);\n}\n\nmi_decl_nodiscard void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_rezalloc_aligned_at(mi_prim_get_default_heap(), p, newsize, alignment, offset);\n}\n\nmi_decl_nodiscard void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept {\n  return mi_heap_rezalloc_aligned(mi_prim_get_default_heap(), p, newsize, alignment);\n}\n\nmi_decl_nodiscard void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {\n  return mi_heap_recalloc_aligned_at(mi_prim_get_default_heap(), p, newcount, size, alignment, offset);\n}\n\nmi_decl_nodiscard void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept {\n  return mi_heap_recalloc_aligned(mi_prim_get_default_heap(), p, newcount, size, alignment);\n}\n\n\n"
  },
  {
    "path": "src/alloc-override.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#if !defined(MI_IN_ALLOC_C)\n#error \"this file should be included from 'alloc.c' (so aliases can work)\"\n#endif\n\n#if defined(MI_MALLOC_OVERRIDE) && defined(_WIN32) && !(defined(MI_SHARED_LIB) && defined(_DLL))\n#error \"It is only possible to override \"malloc\" on Windows when building as a DLL (and linking the C runtime as a DLL)\"\n#endif\n\n#if defined(MI_MALLOC_OVERRIDE) && !(defined(_WIN32))\n\n#if defined(__APPLE__)\n#include <AvailabilityMacros.h>\nmi_decl_externc void   vfree(void* p);\nmi_decl_externc size_t malloc_size(const void* p);\nmi_decl_externc size_t malloc_good_size(size_t size);\n#endif\n\n// helper definition for C override of C++ new\ntypedef void* mi_nothrow_t;\n\n// ------------------------------------------------------\n// Override system malloc\n// ------------------------------------------------------\n\n#if (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__) && !MI_TRACK_ENABLED\n  // gcc, clang: use aliasing to alias the exported function to one of our `mi_` functions\n  #if (defined(__GNUC__) && __GNUC__ >= 9)\n    #pragma GCC diagnostic ignored \"-Wattributes\"  // or we get warnings that nodiscard is ignored on a forward\n    #define MI_FORWARD(fun)      __attribute__((alias(#fun), used, visibility(\"default\"), copy(fun)));\n  #else\n    #define MI_FORWARD(fun)      __attribute__((alias(#fun), used, visibility(\"default\")));\n  #endif\n  #define MI_FORWARD1(fun,x)      MI_FORWARD(fun)\n  #define MI_FORWARD2(fun,x,y)    MI_FORWARD(fun)\n  #define MI_FORWARD3(fun,x,y,z)  MI_FORWARD(fun)\n  #define MI_FORWARD0(fun,x)      MI_FORWARD(fun)\n  #define MI_FORWARD02(fun,x,y)   MI_FORWARD(fun)\n#else\n  // otherwise use forwarding by calling our `mi_` function\n  #define MI_FORWARD1(fun,x)      { return fun(x); }\n  #define MI_FORWARD2(fun,x,y)    { return fun(x,y); }\n  #define MI_FORWARD3(fun,x,y,z)  { return fun(x,y,z); }\n  #define MI_FORWARD0(fun,x)      { fun(x); }\n  #define MI_FORWARD02(fun,x,y)   { fun(x,y); }\n#endif\n\n\n#if defined(__APPLE__) && defined(MI_SHARED_LIB_EXPORT) && defined(MI_OSX_INTERPOSE)\n  // define MI_OSX_IS_INTERPOSED as we should not provide forwarding definitions for\n  // functions that are interposed (or the interposing does not work)\n  #define MI_OSX_IS_INTERPOSED\n\n  mi_decl_externc size_t mi_malloc_size_checked(void *p) {\n    if (!mi_is_in_heap_region(p)) return 0;\n    return mi_usable_size(p);\n  }\n\n  // use interposing so `DYLD_INSERT_LIBRARIES` works without `DYLD_FORCE_FLAT_NAMESPACE=1`\n  // See: <https://books.google.com/books?id=K8vUkpOXhN4C&pg=PA73>\n  struct mi_interpose_s {\n    const void* replacement;\n    const void* target;\n  };\n  #define MI_INTERPOSE_FUN(oldfun,newfun) { (const void*)&newfun, (const void*)&oldfun }\n  #define MI_INTERPOSE_MI(fun)            MI_INTERPOSE_FUN(fun,mi_##fun)\n\n  #define MI_INTERPOSE_DECLS(name)        __attribute__((used)) static struct mi_interpose_s name[]  __attribute__((section(\"__DATA, __interpose\")))\n\n  MI_INTERPOSE_DECLS(_mi_interposes) =\n  {\n    MI_INTERPOSE_MI(malloc),\n    MI_INTERPOSE_MI(calloc),\n    MI_INTERPOSE_MI(realloc),\n    MI_INTERPOSE_MI(strdup),\n    MI_INTERPOSE_MI(realpath),\n    MI_INTERPOSE_MI(posix_memalign),\n    MI_INTERPOSE_MI(reallocf),\n    MI_INTERPOSE_MI(valloc),\n    MI_INTERPOSE_FUN(malloc_size,mi_malloc_size_checked),\n    MI_INTERPOSE_MI(malloc_good_size),\n    #ifdef MI_OSX_ZONE\n    // we interpose malloc_default_zone in alloc-override-osx.c so we can use mi_free safely\n    MI_INTERPOSE_MI(free),\n    MI_INTERPOSE_FUN(vfree,mi_free),\n    #else\n    // sometimes code allocates from default zone but deallocates using plain free :-( (like NxHashResizeToCapacity <https://github.com/nneonneo/osx-10.9-opensource/blob/master/objc4-551.1/runtime/hashtable2.mm>)\n    MI_INTERPOSE_FUN(free,mi_cfree), // use safe free that checks if pointers are from us\n    MI_INTERPOSE_FUN(vfree,mi_cfree),\n    #endif\n  };\n  MI_INTERPOSE_DECLS(_mi_interposes_10_7) __OSX_AVAILABLE(10.7) = {\n    MI_INTERPOSE_MI(strndup),\n  };\n  MI_INTERPOSE_DECLS(_mi_interposes_10_15) __OSX_AVAILABLE(10.15) = {\n    MI_INTERPOSE_MI(aligned_alloc),\n  };\n\n  #ifdef __cplusplus\n  extern \"C\" {\n  #endif\n  void  _ZdlPv(void* p);   // delete\n  void  _ZdaPv(void* p);   // delete[]\n  void  _ZdlPvm(void* p, size_t n);  // delete\n  void  _ZdaPvm(void* p, size_t n);  // delete[]\n  void* _Znwm(size_t n);  // new\n  void* _Znam(size_t n);  // new[]\n  void* _ZnwmRKSt9nothrow_t(size_t n, mi_nothrow_t tag); // new nothrow\n  void* _ZnamRKSt9nothrow_t(size_t n, mi_nothrow_t tag); // new[] nothrow\n  #ifdef __cplusplus\n  }\n  #endif\n  __attribute__((used)) static struct mi_interpose_s _mi_cxx_interposes[]  __attribute__((section(\"__DATA, __interpose\"))) =\n  {\n    MI_INTERPOSE_FUN(_ZdlPv,mi_free),\n    MI_INTERPOSE_FUN(_ZdaPv,mi_free),\n    MI_INTERPOSE_FUN(_ZdlPvm,mi_free_size),\n    MI_INTERPOSE_FUN(_ZdaPvm,mi_free_size),\n    MI_INTERPOSE_FUN(_Znwm,mi_new),\n    MI_INTERPOSE_FUN(_Znam,mi_new),\n    MI_INTERPOSE_FUN(_ZnwmRKSt9nothrow_t,mi_new_nothrow),\n    MI_INTERPOSE_FUN(_ZnamRKSt9nothrow_t,mi_new_nothrow),\n  };\n\n#elif defined(_MSC_VER)\n  // cannot override malloc unless using a dll.\n  // we just override new/delete which does work in a static library.\n#else\n  // On all other systems forward allocation primitives to our API\n  mi_decl_export void* malloc(size_t size)              MI_FORWARD1(mi_malloc, size)\n  mi_decl_export void* calloc(size_t size, size_t n)    MI_FORWARD2(mi_calloc, size, n)\n  mi_decl_export void* realloc(void* p, size_t newsize) MI_FORWARD2(mi_realloc, p, newsize)\n  mi_decl_export void  free(void* p)                    MI_FORWARD0(mi_free, p)  \n  // In principle we do not need to forward `strdup`/`strndup` but on some systems these do not use `malloc` internally (but a more primitive call)\n  // We only override if `strdup` is not a macro (as on some older libc's, see issue #885)\n  #if !defined(strdup)\n  mi_decl_export char* strdup(const char* str)             MI_FORWARD1(mi_strdup, str)\n  #endif\n  #if !defined(strndup) && (!defined(__APPLE__) || (defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7))\n  mi_decl_export char* strndup(const char* str, size_t n)  MI_FORWARD2(mi_strndup, str, n)   \n  #endif\n#endif\n\n#if (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__)\n#pragma GCC visibility push(default)\n#endif\n\n// ------------------------------------------------------\n// Override new/delete\n// This is not really necessary as they usually call\n// malloc/free anyway, but it improves performance.\n// ------------------------------------------------------\n#ifdef __cplusplus\n  // ------------------------------------------------------\n  // With a C++ compiler we override the new/delete operators.\n  // see <https://en.cppreference.com/w/cpp/memory/new/operator_new>\n  // ------------------------------------------------------\n  #include <new>\n\n  #ifndef MI_OSX_IS_INTERPOSED\n    void operator delete(void* p) noexcept              MI_FORWARD0(mi_free,p)\n    void operator delete[](void* p) noexcept            MI_FORWARD0(mi_free,p)\n\n    void* operator new(std::size_t n) noexcept(false)   MI_FORWARD1(mi_new,n)\n    void* operator new[](std::size_t n) noexcept(false) MI_FORWARD1(mi_new,n)\n\n    void* operator new  (std::size_t n, const std::nothrow_t& tag) noexcept { MI_UNUSED(tag); return mi_new_nothrow(n); }\n    void* operator new[](std::size_t n, const std::nothrow_t& tag) noexcept { MI_UNUSED(tag); return mi_new_nothrow(n); }\n\n    #if (__cplusplus >= 201402L || _MSC_VER >= 1916)\n    void operator delete  (void* p, std::size_t n) noexcept MI_FORWARD02(mi_free_size,p,n)\n    void operator delete[](void* p, std::size_t n) noexcept MI_FORWARD02(mi_free_size,p,n)\n    #endif\n  #endif\n\n  #if (__cplusplus > 201402L && defined(__cpp_aligned_new)) && (!defined(__GNUC__) || (__GNUC__ > 5))\n  void operator delete  (void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete[](void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete  (void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast<size_t>(al)); };\n  void operator delete[](void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast<size_t>(al)); };\n  void operator delete  (void* p, std::align_val_t al, const std::nothrow_t&) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n  void operator delete[](void* p, std::align_val_t al, const std::nothrow_t&) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }\n\n  void* operator new( std::size_t n, std::align_val_t al)   noexcept(false) { return mi_new_aligned(n, static_cast<size_t>(al)); }\n  void* operator new[]( std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast<size_t>(al)); }\n  void* operator new  (std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast<size_t>(al)); }\n  void* operator new[](std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast<size_t>(al)); }\n  #endif\n\n#elif (defined(__GNUC__) || defined(__clang__))\n  // ------------------------------------------------------\n  // Override by defining the mangled C++ names of the operators (as\n  // used by GCC and CLang).\n  // See <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>\n  // ------------------------------------------------------\n\n  void _ZdlPv(void* p)            MI_FORWARD0(mi_free,p) // delete\n  void _ZdaPv(void* p)            MI_FORWARD0(mi_free,p) // delete[]\n  void _ZdlPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n)\n  void _ZdaPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n)\n  \n  void _ZdlPvSt11align_val_t(void* p, size_t al)            { mi_free_aligned(p,al); }\n  void _ZdaPvSt11align_val_t(void* p, size_t al)            { mi_free_aligned(p,al); }\n  void _ZdlPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); }\n  void _ZdaPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); }\n\n  void _ZdlPvRKSt9nothrow_t(void* p, mi_nothrow_t tag)      { MI_UNUSED(tag); mi_free(p); }  // operator delete(void*, std::nothrow_t const&) \n  void _ZdaPvRKSt9nothrow_t(void* p, mi_nothrow_t tag)      { MI_UNUSED(tag); mi_free(p); }  // operator delete[](void*, std::nothrow_t const&)\n  void _ZdlPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete(void*, std::align_val_t, std::nothrow_t const&) \n  void _ZdaPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete[](void*, std::align_val_t, std::nothrow_t const&) \n  \n  #if (MI_INTPTR_SIZE==8)\n    void* _Znwm(size_t n)                             MI_FORWARD1(mi_new,n)  // new 64-bit\n    void* _Znam(size_t n)                             MI_FORWARD1(mi_new,n)  // new[] 64-bit\n    void* _ZnwmRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }\n    void* _ZnamRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }\n    void* _ZnwmSt11align_val_t(size_t n, size_t al)   MI_FORWARD2(mi_new_aligned, n, al)\n    void* _ZnamSt11align_val_t(size_t n, size_t al)   MI_FORWARD2(mi_new_aligned, n, al)\n    void* _ZnwmSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }\n    void* _ZnamSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }\n  #elif (MI_INTPTR_SIZE==4)\n    void* _Znwj(size_t n)                             MI_FORWARD1(mi_new,n)  // new 64-bit\n    void* _Znaj(size_t n)                             MI_FORWARD1(mi_new,n)  // new[] 64-bit\n    void* _ZnwjRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }\n    void* _ZnajRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }\n    void* _ZnwjSt11align_val_t(size_t n, size_t al)   MI_FORWARD2(mi_new_aligned, n, al)\n    void* _ZnajSt11align_val_t(size_t n, size_t al)   MI_FORWARD2(mi_new_aligned, n, al)\n    void* _ZnwjSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }\n    void* _ZnajSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }\n  #else\n    #error \"define overloads for new/delete for this platform (just for performance, can be skipped)\"\n  #endif\n#endif // __cplusplus\n\n// ------------------------------------------------------\n// Further Posix & Unix functions definitions\n// ------------------------------------------------------\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef MI_OSX_IS_INTERPOSED\n  // Forward Posix/Unix calls as well\n  void*  reallocf(void* p, size_t newsize) MI_FORWARD2(mi_reallocf,p,newsize)\n  size_t malloc_size(const void* p)        MI_FORWARD1(mi_usable_size,p)\n  #if !defined(__ANDROID__) && !defined(__FreeBSD__) && !defined(__DragonFly__)\n  size_t malloc_usable_size(void *p)       MI_FORWARD1(mi_usable_size,p)\n  #else\n  size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_usable_size,p)\n  #endif\n\n  // No forwarding here due to aliasing/name mangling issues\n  void*  valloc(size_t size)               { return mi_valloc(size); }\n  void   vfree(void* p)                    { mi_free(p); }\n  size_t malloc_good_size(size_t size)     { return mi_malloc_good_size(size); }\n  int    posix_memalign(void** p, size_t alignment, size_t size) { return mi_posix_memalign(p, alignment, size); }\n\n  // `aligned_alloc` is only available when __USE_ISOC11 is defined.\n  // Note: it seems __USE_ISOC11 is not defined in musl (and perhaps other libc's) so we only check\n  // for it if using glibc.\n  // Note: Conda has a custom glibc where `aligned_alloc` is declared `static inline` and we cannot\n  // override it, but both _ISOC11_SOURCE and __USE_ISOC11 are undefined in Conda GCC7 or GCC9.\n  // Fortunately, in the case where `aligned_alloc` is declared as `static inline` it\n  // uses internally `memalign`, `posix_memalign`, or `_aligned_malloc` so we  can avoid overriding it ourselves.\n  #if !defined(__GLIBC__) || __USE_ISOC11\n  void* aligned_alloc(size_t alignment, size_t size) { return mi_aligned_alloc(alignment, size); }\n  #endif\n#endif\n\n// no forwarding here due to aliasing/name mangling issues\nvoid  cfree(void* p)                                    { mi_free(p); }\nvoid* pvalloc(size_t size)                              { return mi_pvalloc(size); }\nvoid* memalign(size_t alignment, size_t size)           { return mi_memalign(alignment, size); }\nvoid* _aligned_malloc(size_t alignment, size_t size)    { return mi_aligned_alloc(alignment, size); }\nvoid* reallocarray(void* p, size_t count, size_t size)  { return mi_reallocarray(p, count, size); }\n// some systems define reallocarr so mark it as a weak symbol (#751)\nmi_decl_weak int reallocarr(void* p, size_t count, size_t size)    { return mi_reallocarr(p, count, size); }\n\n#if defined(__wasi__)\n  // forward __libc interface (see PR #667)\n  void* __libc_malloc(size_t size)                      MI_FORWARD1(mi_malloc, size)\n  void* __libc_calloc(size_t count, size_t size)        MI_FORWARD2(mi_calloc, count, size)\n  void* __libc_realloc(void* p, size_t size)            MI_FORWARD2(mi_realloc, p, size)\n  void  __libc_free(void* p)                            MI_FORWARD0(mi_free, p)\n  void* __libc_memalign(size_t alignment, size_t size)  { return mi_memalign(alignment, size); }\n\n#elif defined(__linux__)\n  // forward __libc interface (needed for glibc-based and musl-based Linux distributions)\n  void* __libc_malloc(size_t size)                      MI_FORWARD1(mi_malloc,size)\n  void* __libc_calloc(size_t count, size_t size)        MI_FORWARD2(mi_calloc,count,size)\n  void* __libc_realloc(void* p, size_t size)            MI_FORWARD2(mi_realloc,p,size)\n  void  __libc_free(void* p)                            MI_FORWARD0(mi_free,p)\n  void  __libc_cfree(void* p)                           MI_FORWARD0(mi_free,p)\n\n  void* __libc_valloc(size_t size)                      { return mi_valloc(size); }\n  void* __libc_pvalloc(size_t size)                     { return mi_pvalloc(size); }\n  void* __libc_memalign(size_t alignment, size_t size)  { return mi_memalign(alignment,size); }\n  int   __posix_memalign(void** p, size_t alignment, size_t size) { return mi_posix_memalign(p,alignment,size); }\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#if (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__)\n#pragma GCC visibility pop\n#endif\n\n#endif // MI_MALLOC_OVERRIDE && !_WIN32\n"
  },
  {
    "path": "src/alloc-posix.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// ------------------------------------------------------------------------\n// mi prefixed publi definitions of various Posix, Unix, and C++ functions\n// for convenience and used when overriding these functions.\n// ------------------------------------------------------------------------\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n\n// ------------------------------------------------------\n// Posix & Unix functions definitions\n// ------------------------------------------------------\n\n#include <errno.h>\n#include <string.h>  // memset\n#include <stdlib.h>  // getenv\n\n#ifdef _MSC_VER\n#pragma warning(disable:4996)  // getenv _wgetenv\n#endif\n\n#ifndef EINVAL\n#define EINVAL 22\n#endif\n#ifndef ENOMEM\n#define ENOMEM 12\n#endif\n\n\nmi_decl_nodiscard size_t mi_malloc_size(const void* p) mi_attr_noexcept {\n  // if (!mi_is_in_heap_region(p)) return 0;\n  return mi_usable_size(p);\n}\n\nmi_decl_nodiscard size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept {\n  // if (!mi_is_in_heap_region(p)) return 0;\n  return mi_usable_size(p);\n}\n\nmi_decl_nodiscard size_t mi_malloc_good_size(size_t size) mi_attr_noexcept {\n  return mi_good_size(size);\n}\n\nvoid mi_cfree(void* p) mi_attr_noexcept {\n  if (mi_is_in_heap_region(p)) {\n    mi_free(p);\n  }\n}\n\nint mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept {\n  // Note: The spec dictates we should not modify `*p` on an error. (issue#27)\n  // <http://man7.org/linux/man-pages/man3/posix_memalign.3.html>\n  if (p == NULL) return EINVAL;\n  if ((alignment % sizeof(void*)) != 0) return EINVAL;                 // natural alignment\n  // it is also required that alignment is a power of 2 and > 0; this is checked in `mi_malloc_aligned`\n  if (alignment==0 || !_mi_is_power_of_two(alignment)) return EINVAL;  // not a power of 2\n  void* q = mi_malloc_aligned(size, alignment);\n  if (q==NULL && size != 0) return ENOMEM;\n  mi_assert_internal(((uintptr_t)q % alignment) == 0);\n  *p = q;\n  return 0;\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept {\n  void* p = mi_malloc_aligned(size, alignment);\n  mi_assert_internal(((uintptr_t)p % alignment) == 0);\n  return p;\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept {\n  return mi_memalign( _mi_os_page_size(), size );\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept {\n  size_t psize = _mi_os_page_size();\n  if (size >= SIZE_MAX - psize) return NULL; // overflow\n  size_t asize = _mi_align_up(size, psize);\n  return mi_malloc_aligned(asize, psize);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept {\n  // C11 requires the size to be an integral multiple of the alignment, see <https://en.cppreference.com/w/c/memory/aligned_alloc>.\n  // unfortunately, it turns out quite some programs pass a size that is not an integral multiple so skip this check..\n  /* if mi_unlikely((size & (alignment - 1)) != 0) { // C11 requires alignment>0 && integral multiple, see <https://en.cppreference.com/w/c/memory/aligned_alloc>\n      #if MI_DEBUG > 0\n      _mi_error_message(EOVERFLOW, \"(mi_)aligned_alloc requires the size to be an integral multiple of the alignment (size %zu, alignment %zu)\\n\", size, alignment);\n      #endif\n      return NULL;\n    }\n  */\n  // C11 also requires alignment to be a power-of-two (and > 0) which is checked in mi_malloc_aligned\n  void* p = mi_malloc_aligned(size, alignment);\n  mi_assert_internal(((uintptr_t)p % alignment) == 0);\n  return p;\n}\n\nmi_decl_nodiscard void* mi_reallocarray( void* p, size_t count, size_t size ) mi_attr_noexcept {  // BSD\n  void* newp = mi_reallocn(p,count,size);\n  if (newp==NULL) { errno = ENOMEM; }\n  return newp;\n}\n\nmi_decl_nodiscard int mi_reallocarr( void* p, size_t count, size_t size ) mi_attr_noexcept { // NetBSD\n  mi_assert(p != NULL);\n  if (p == NULL) {\n    errno = EINVAL;\n    return EINVAL;\n  }\n  void** op = (void**)p;\n  void* newp = mi_reallocarray(*op, count, size);\n  if mi_unlikely(newp == NULL) { return errno; }\n  *op = newp;\n  return 0;\n}\n\nvoid* mi__expand(void* p, size_t newsize) mi_attr_noexcept {  // Microsoft\n  void* res = mi_expand(p, newsize);\n  if (res == NULL) { errno = ENOMEM; }\n  return res;\n}\n\nmi_decl_nodiscard mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept {\n  if (s==NULL) return NULL;\n  size_t len;\n  for(len = 0; s[len] != 0; len++) { }\n  size_t size = (len+1)*sizeof(unsigned short);\n  unsigned short* p = (unsigned short*)mi_malloc(size);\n  if (p != NULL) {\n    _mi_memcpy(p,s,size);\n  }\n  return p;\n}\n\nmi_decl_nodiscard mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s)  mi_attr_noexcept {\n  return (unsigned char*)mi_strdup((const char*)s);\n}\n\nint mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept {\n  if (buf==NULL || name==NULL) return EINVAL;\n  if (size != NULL) *size = 0;\n  char* p = getenv(name);        // mscver warning 4996\n  if (p==NULL) {\n    *buf = NULL;\n  }\n  else {\n    *buf = mi_strdup(p);\n    if (*buf==NULL) return ENOMEM;\n    if (size != NULL) *size = _mi_strlen(p);\n  }\n  return 0;\n}\n\nint mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept {\n  if (buf==NULL || name==NULL) return EINVAL;\n  if (size != NULL) *size = 0;\n#if !defined(_WIN32) || (defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP))\n  // not supported\n  *buf = NULL;\n  return EINVAL;\n#else\n  unsigned short* p = (unsigned short*)_wgetenv((const wchar_t*)name);  // msvc warning 4996\n  if (p==NULL) {\n    *buf = NULL;\n  }\n  else {\n    *buf = mi_wcsdup(p);\n    if (*buf==NULL) return ENOMEM;\n    if (size != NULL) *size = wcslen((const wchar_t*)p);\n  }\n  return 0;\n#endif\n}\n\nmi_decl_nodiscard void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { // Microsoft\n  return mi_recalloc_aligned_at(p, newcount, size, alignment, offset);\n}\n\nmi_decl_nodiscard void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { // Microsoft\n  return mi_recalloc_aligned(p, newcount, size, alignment);\n}\n"
  },
  {
    "path": "src/alloc.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#ifndef _DEFAULT_SOURCE\n#define _DEFAULT_SOURCE   // for realpath() on Linux\n#endif\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"   // _mi_prim_thread_id()\n\n#include <string.h>      // memset, strlen (for mi_strdup)\n#include <stdlib.h>      // malloc, abort\n\n#define MI_IN_ALLOC_C\n#include \"alloc-override.c\"\n#include \"free.c\"\n#undef MI_IN_ALLOC_C\n\n// ------------------------------------------------------\n// Allocation\n// ------------------------------------------------------\n\n// Fast allocation in a page: just pop from the free list.\n// Fall back to generic allocation only if the list is empty.\n// Note: in release mode the (inlined) routine is about 7 instructions with a single test.\nextern inline void* _mi_page_malloc_zero(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero, size_t* usable) mi_attr_noexcept\n{\n  mi_assert_internal(size >= MI_PADDING_SIZE);\n  mi_assert_internal(page->block_size == 0 /* empty heap */ || mi_page_block_size(page) >= size);\n\n  // check the free list\n  mi_block_t* const block = page->free;\n  if mi_unlikely(block == NULL) {\n    return _mi_malloc_generic(heap, size, zero, 0, usable);\n  }\n  mi_assert_internal(block != NULL && _mi_ptr_page(block) == page);\n  if (usable != NULL) { *usable = mi_page_usable_block_size(page); };\n  // pop from the free list\n  page->free = mi_block_next(page, block);\n  page->used++;\n  mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page);\n  mi_assert_internal(page->block_size < MI_MAX_ALIGN_SIZE || _mi_is_aligned(block, MI_MAX_ALIGN_SIZE));\n\n  #if MI_DEBUG>3\n  if (page->free_is_zero && size > sizeof(*block)) {\n    mi_assert_expensive(mi_mem_is_zero(block+1,size - sizeof(*block)));\n  }\n  #endif\n\n  // allow use of the block internally\n  // note: when tracking we need to avoid ever touching the MI_PADDING since\n  // that is tracked by valgrind etc. as non-accessible (through the red-zone, see `mimalloc/track.h`)\n  mi_track_mem_undefined(block, mi_page_usable_block_size(page));\n\n  // zero the block? note: we need to zero the full block size (issue #63)\n  if mi_unlikely(zero) {\n    mi_assert_internal(page->block_size != 0); // do not call with zero'ing for huge blocks (see _mi_malloc_generic)\n    #if MI_PADDING\n    mi_assert_internal(page->block_size >= MI_PADDING_SIZE);\n    #endif\n    if (page->free_is_zero) {\n      block->next = 0;\n      mi_track_mem_defined(block, page->block_size - MI_PADDING_SIZE);\n    }\n    else {\n      _mi_memzero_aligned(block, page->block_size - MI_PADDING_SIZE);\n    }\n  }\n\n  #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN\n  if (!zero && !mi_page_is_huge(page)) {\n    memset(block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page));\n  }\n  #elif (MI_SECURE!=0)\n  if (!zero) { block->next = 0; } // don't leak internal data\n  #endif\n\n  #if (MI_STAT>0)\n  const size_t bsize = mi_page_usable_block_size(page);\n  if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {\n    mi_heap_stat_increase(heap, malloc_normal, bsize);\n    mi_heap_stat_counter_increase(heap, malloc_normal_count, 1);\n    #if (MI_STAT>1)\n    const size_t bin = _mi_bin(bsize);\n    mi_heap_stat_increase(heap, malloc_bins[bin], 1);\n    mi_heap_stat_increase(heap, malloc_requested, size - MI_PADDING_SIZE);\n    #endif\n  }\n  #endif\n\n  #if MI_PADDING // && !MI_TRACK_ENABLED\n    mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page));\n    ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE));\n    #if (MI_DEBUG>=2)\n    mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta));\n    #endif\n    mi_track_mem_defined(padding,sizeof(mi_padding_t));  // note: re-enable since mi_page_usable_block_size may set noaccess\n    padding->canary = mi_ptr_encode_canary(page,block,page->keys);\n    padding->delta  = (uint32_t)(delta);\n    #if MI_PADDING_CHECK\n    if (!mi_page_is_huge(page)) {\n      uint8_t* fill = (uint8_t*)padding - delta;\n      const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes\n      for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; }\n    }\n    #endif\n  #endif\n\n  return block;\n}\n\n// extra entries for improved efficiency in `alloc-aligned.c`.\nextern void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {\n  return _mi_page_malloc_zero(heap,page,size,false,NULL);\n}\nextern void* _mi_page_malloc_zeroed(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {\n  return _mi_page_malloc_zero(heap,page,size,true,NULL);\n}\n\n#if MI_GUARDED\nmi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept;\n#endif\n\nstatic inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap, size_t size, bool zero, size_t* usable) mi_attr_noexcept {\n  mi_assert(heap != NULL);\n  mi_assert(size <= MI_SMALL_SIZE_MAX);\n  #if MI_DEBUG\n  const uintptr_t tid = _mi_thread_id();\n  mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local\n  #endif\n  #if (MI_PADDING || MI_GUARDED)\n  if (size == 0) { size = sizeof(void*); }\n  #endif\n  #if MI_GUARDED\n  if (mi_heap_malloc_use_guarded(heap,size)) {\n    return _mi_heap_malloc_guarded(heap, size, zero);\n  }\n  #endif\n\n  // get page in constant time, and allocate from it\n  mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);\n  void* const p = _mi_page_malloc_zero(heap, page, size + MI_PADDING_SIZE, zero, usable);\n  mi_track_malloc(p,size,zero);\n\n  #if MI_DEBUG>3\n  if (p != NULL && zero) {\n    mi_assert_expensive(mi_mem_is_zero(p, size));\n  }\n  #endif\n  return p;\n}\n\n// allocate a small block\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept {\n  return mi_heap_malloc_small_zero(heap, size, false, NULL);\n}\n\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept {\n  return mi_heap_malloc_small(mi_prim_get_default_heap(), size);\n}\n\n// The main allocation function\nextern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, size_t* usable) mi_attr_noexcept {\n  // fast path for small objects\n  if mi_likely(size <= MI_SMALL_SIZE_MAX) {\n    mi_assert_internal(huge_alignment == 0);\n    return mi_heap_malloc_small_zero(heap, size, zero, usable);\n  }\n  #if MI_GUARDED\n  else if (huge_alignment==0 && mi_heap_malloc_use_guarded(heap,size)) {\n    return _mi_heap_malloc_guarded(heap, size, zero);\n  }\n  #endif\n  else {\n    // regular allocation\n    mi_assert(heap!=NULL);\n    mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id());   // heaps are thread local\n    void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment, usable);  // note: size can overflow but it is detected in malloc_generic\n    mi_track_malloc(p,size,zero);\n\n    #if MI_DEBUG>3\n    if (p != NULL && zero) {\n      mi_assert_expensive(mi_mem_is_zero(p, size));\n    }\n    #endif\n    return p;\n  }\n}\n\nextern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {\n  return _mi_heap_malloc_zero_ex(heap, size, zero, 0, NULL);\n}\n\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {\n  return _mi_heap_malloc_zero(heap, size, false);\n}\n\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept {\n  return mi_heap_malloc(mi_prim_get_default_heap(), size);\n}\n\n// zero initialized small block\nmi_decl_nodiscard mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept {\n  return mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, true, NULL);\n}\n\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {\n  return _mi_heap_malloc_zero(heap, size, true);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept {\n  return mi_heap_zalloc(mi_prim_get_default_heap(),size);\n}\n\n\nmi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count,size,&total)) return NULL;\n  return mi_heap_zalloc(heap,total);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept {\n  return mi_heap_calloc(mi_prim_get_default_heap(),count,size);\n}\n\n// Return usable size\nmi_decl_nodiscard mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* usable) mi_attr_noexcept {\n  return mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, false, usable);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_umalloc(mi_heap_t* heap, size_t size, size_t* usable) mi_attr_noexcept {\n  return _mi_heap_malloc_zero_ex(heap, size, false, 0, usable);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_umalloc(size_t size, size_t* usable) mi_attr_noexcept {\n  return mi_heap_umalloc(mi_prim_get_default_heap(), size, usable);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_uzalloc(size_t size, size_t* usable) mi_attr_noexcept {\n  return _mi_heap_malloc_zero_ex(mi_prim_get_default_heap(), size, true, 0, usable);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* usable) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count,size,&total)) return NULL;\n  return mi_uzalloc(total, usable);\n}\n\n// Uninitialized `calloc`\nmi_decl_nodiscard extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count, size, &total)) return NULL;\n  return mi_heap_malloc(heap, total);\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept {\n  return mi_heap_mallocn(mi_prim_get_default_heap(),count,size);\n}\n\n// Expand (or shrink) in place (or fail)\nvoid* mi_expand(void* p, size_t newsize) mi_attr_noexcept {\n  #if MI_PADDING\n  // we do not shrink/expand with padding enabled\n  MI_UNUSED(p); MI_UNUSED(newsize);\n  return NULL;\n  #else\n  if (p == NULL) return NULL;\n  const mi_page_t* const page = mi_validate_ptr_page(p,\"mi_expand\");  \n  const size_t size = _mi_usable_size(p,page);\n  if (newsize > size) return NULL;\n  return p; // it fits\n  #endif\n}\n\nvoid* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero, size_t* usable_pre, size_t* usable_post) mi_attr_noexcept {\n  // if p == NULL then behave as malloc.\n  // else if size == 0 then reallocate to a zero-sized block (and don't return NULL, just as mi_malloc(0)).\n  // (this means that returning NULL always indicates an error, and `p` will not have been freed in that case.)\n  const mi_page_t* page;\n  size_t size;\n  if (p==NULL) {\n    page = NULL;\n    size = 0;\n    if (usable_pre!=NULL) { *usable_pre = 0; }\n  }\n  else {    \n    page = mi_validate_ptr_page(p,\"mi_realloc\");  \n    size = _mi_usable_size(p,page);\n    if (usable_pre!=NULL) { *usable_pre = mi_page_usable_block_size(page); }    \n  }\n  if mi_unlikely(newsize <= size && newsize >= (size / 2) && newsize > 0) {  // note: newsize must be > 0 or otherwise we return NULL for realloc(NULL,0)\n    mi_assert_internal(p!=NULL);\n    // todo: do not track as the usable size is still the same in the free; adjust potential padding?\n    // mi_track_resize(p,size,newsize)\n    // if (newsize < size) { mi_track_mem_noaccess((uint8_t*)p + newsize, size - newsize); }\n    if (usable_post!=NULL) { *usable_post = mi_page_usable_block_size(page); }\n    return p;  // reallocation still fits and not more than 50% waste\n  }\n  void* newp = mi_heap_umalloc(heap,newsize,usable_post);\n  if mi_likely(newp != NULL) {\n    if (zero && newsize > size) {\n      // also set last word in the previous allocation to zero to ensure any padding is zero-initialized\n      const size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0);\n      _mi_memzero((uint8_t*)newp + start, newsize - start);\n    }\n    else if (newsize == 0) {\n      ((uint8_t*)newp)[0] = 0; // work around for applications that expect zero-reallocation to be zero initialized (issue #725)\n    }\n    if mi_likely(p != NULL) {\n      const size_t copysize = (newsize > size ? size : newsize);\n      mi_track_mem_defined(p,copysize);  // _mi_useable_size may be too large for byte precise memory tracking..\n      _mi_memcpy(newp, p, copysize);\n      mi_free(p); // only free the original pointer if successful\n    }\n  }\n  return newp;\n}\n\nmi_decl_nodiscard void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {\n  return _mi_heap_realloc_zero(heap, p, newsize, false, NULL, NULL);\n}\n\nmi_decl_nodiscard void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count, size, &total)) return NULL;\n  return mi_heap_realloc(heap, p, total);\n}\n\n\n// Reallocate but free `p` on errors\nmi_decl_nodiscard void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {\n  void* newp = mi_heap_realloc(heap, p, newsize);\n  if (newp==NULL && p!=NULL) mi_free(p);\n  return newp;\n}\n\nmi_decl_nodiscard void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {\n  return _mi_heap_realloc_zero(heap, p, newsize, true, NULL, NULL);\n}\n\nmi_decl_nodiscard void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {\n  size_t total;\n  if (mi_count_size_overflow(count, size, &total)) return NULL;\n  return mi_heap_rezalloc(heap, p, total);\n}\n\n\nmi_decl_nodiscard void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept {\n  return mi_heap_realloc(mi_prim_get_default_heap(),p,newsize);\n}\n\nmi_decl_nodiscard void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept {\n  return mi_heap_reallocn(mi_prim_get_default_heap(),p,count,size);\n}\n\nmi_decl_nodiscard void* mi_urealloc(void* p, size_t newsize, size_t* usable_pre, size_t* usable_post) mi_attr_noexcept {\n  return _mi_heap_realloc_zero(mi_prim_get_default_heap(),p,newsize, false, usable_pre, usable_post);\n}\n\n// Reallocate but free `p` on errors\nmi_decl_nodiscard void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept {\n  return mi_heap_reallocf(mi_prim_get_default_heap(),p,newsize);\n}\n\nmi_decl_nodiscard void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept {\n  return mi_heap_rezalloc(mi_prim_get_default_heap(), p, newsize);\n}\n\nmi_decl_nodiscard void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept {\n  return mi_heap_recalloc(mi_prim_get_default_heap(), p, count, size);\n}\n\n\n\n// ------------------------------------------------------\n// strdup, strndup, and realpath\n// ------------------------------------------------------\n\n// `strdup` using mi_malloc\nmi_decl_nodiscard mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept {\n  if (s == NULL) return NULL;\n  size_t len = _mi_strlen(s);\n  char* t = (char*)mi_heap_malloc(heap,len+1);\n  if (t == NULL) return NULL;\n  _mi_memcpy(t, s, len);\n  t[len] = 0;\n  return t;\n}\n\nmi_decl_nodiscard mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept {\n  return mi_heap_strdup(mi_prim_get_default_heap(), s);\n}\n\n// `strndup` using mi_malloc\nmi_decl_nodiscard mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept {\n  if (s == NULL) return NULL;\n  const size_t len = _mi_strnlen(s,n);  // len <= n\n  char* t = (char*)mi_heap_malloc(heap, len+1);\n  if (t == NULL) return NULL;\n  _mi_memcpy(t, s, len);\n  t[len] = 0;\n  return t;\n}\n\nmi_decl_nodiscard mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept {\n  return mi_heap_strndup(mi_prim_get_default_heap(),s,n);\n}\n\n#ifndef __wasi__\n// `realpath` using mi_malloc\n#ifdef _WIN32\n#ifndef PATH_MAX\n#define PATH_MAX MAX_PATH\n#endif\n\nmi_decl_nodiscard mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {\n  // todo: use GetFullPathNameW to allow longer file names\n  char buf[PATH_MAX];\n  DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL);\n  if (res == 0) {\n    errno = GetLastError(); return NULL;\n  }\n  else if (res > PATH_MAX) {\n    errno = EINVAL; return NULL;\n  }\n  else if (resolved_name != NULL) {\n    return resolved_name;\n  }\n  else {\n    return mi_heap_strndup(heap, buf, PATH_MAX);\n  }\n}\n#else\n/*\n#include <unistd.h>  // pathconf\nstatic size_t mi_path_max(void) {\n  static size_t path_max = 0;\n  if (path_max <= 0) {\n    long m = pathconf(\"/\",_PC_PATH_MAX);\n    if (m <= 0) path_max = 4096;      // guess\n    else if (m < 256) path_max = 256; // at least 256\n    else path_max = m;\n  }\n  return path_max;\n}\n*/\nchar* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {\n  if (resolved_name != NULL) {\n    return realpath(fname,resolved_name);\n  }\n  else {\n    char* rname = realpath(fname, NULL);\n    if (rname == NULL) return NULL;\n    char* result = mi_heap_strdup(heap, rname);\n    mi_cfree(rname);  // use checked free (which may be redirected to our free but that's ok)\n    // note: with ASAN realpath is intercepted and mi_cfree may leak the returned pointer :-(\n    return result;\n  }\n  /*\n    const size_t n  = mi_path_max();\n    char* buf = (char*)mi_malloc(n+1);\n    if (buf == NULL) {\n      errno = ENOMEM;\n      return NULL;\n    }\n    char* rname  = realpath(fname,buf);\n    char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL`\n    mi_free(buf);\n    return result;\n  }\n  */\n}\n#endif\n\nmi_decl_nodiscard mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept {\n  return mi_heap_realpath(mi_prim_get_default_heap(),fname,resolved_name);\n}\n#endif\n\n/*-------------------------------------------------------\nC++ new and new_aligned\nThe standard requires calling into `get_new_handler` and\nthrowing the bad_alloc exception on failure. If we compile\nwith a C++ compiler we can implement this precisely. If we\nuse a C compiler we cannot throw a `bad_alloc` exception\nbut we call `exit` instead (i.e. not returning).\n-------------------------------------------------------*/\n\n#ifdef __cplusplus\n#include <new>\nstatic bool mi_try_new_handler(bool nothrow) {\n  #if defined(_MSC_VER) || (__cplusplus >= 201103L)\n    std::new_handler h = std::get_new_handler();\n  #else\n    std::new_handler h = std::set_new_handler();\n    std::set_new_handler(h);\n  #endif\n  if (h==NULL) {\n    _mi_error_message(ENOMEM, \"out of memory in 'new'\");\n    #if defined(_CPPUNWIND) || defined(__cpp_exceptions)  // exceptions are not always enabled\n    if (!nothrow) {\n      throw std::bad_alloc();\n    }\n    #else\n    MI_UNUSED(nothrow);\n    #endif\n    return false;\n  }\n  else {\n    h();\n    return true;\n  }\n}\n#else\ntypedef void (*std_new_handler_t)(void);\n\n#if (defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)))  // exclude clang-cl, see issue #631\nstd_new_handler_t __attribute__((weak)) _ZSt15get_new_handlerv(void) {\n  return NULL;\n}\nstatic std_new_handler_t mi_get_new_handler(void) {\n  return _ZSt15get_new_handlerv();\n}\n#else\n// note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`.\nstatic std_new_handler_t mi_get_new_handler(void) {\n  return NULL;\n}\n#endif\n\nstatic bool mi_try_new_handler(bool nothrow) {\n  std_new_handler_t h = mi_get_new_handler();\n  if (h==NULL) {\n    _mi_error_message(ENOMEM, \"out of memory in 'new'\");\n    if (!nothrow) {\n      abort();  // cannot throw in plain C, use abort\n    }\n    return false;\n  }\n  else {\n    h();\n    return true;\n  }\n}\n#endif\n\nmi_decl_export mi_decl_noinline void* mi_heap_try_new(mi_heap_t* heap, size_t size, bool nothrow ) {\n  void* p = NULL;\n  while(p == NULL && mi_try_new_handler(nothrow)) {\n    p = mi_heap_malloc(heap,size);\n  }\n  return p;\n}\n\nstatic mi_decl_noinline void* mi_try_new(size_t size, bool nothrow) {\n  return mi_heap_try_new(mi_prim_get_default_heap(), size, nothrow);\n}\n\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) {\n  void* p = mi_heap_malloc(heap,size);\n  if mi_unlikely(p == NULL) return mi_heap_try_new(heap, size, false);\n  return p;\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_new(size_t size) {\n  return mi_heap_alloc_new(mi_prim_get_default_heap(), size);\n}\n\n\nmi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) {\n  size_t total;\n  if mi_unlikely(mi_count_size_overflow(count, size, &total)) {\n    mi_try_new_handler(false);  // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc\n    return NULL;\n  }\n  else {\n    return mi_heap_alloc_new(heap,total);\n  }\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_new_n(size_t count, size_t size) {\n  return mi_heap_alloc_new_n(mi_prim_get_default_heap(), count, size);\n}\n\n\nmi_decl_nodiscard mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept {\n  void* p = mi_malloc(size);\n  if mi_unlikely(p == NULL) return mi_try_new(size, true);\n  return p;\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) {\n  void* p;\n  do {\n    p = mi_malloc_aligned(size, alignment);\n  }\n  while(p == NULL && mi_try_new_handler(false));\n  return p;\n}\n\nmi_decl_nodiscard mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept {\n  void* p;\n  do {\n    p = mi_malloc_aligned(size, alignment);\n  }\n  while(p == NULL && mi_try_new_handler(true));\n  return p;\n}\n\nmi_decl_nodiscard void* mi_new_realloc(void* p, size_t newsize) {\n  void* q;\n  do {\n    q = mi_realloc(p, newsize);\n  } while (q == NULL && mi_try_new_handler(false));\n  return q;\n}\n\nmi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) {\n  size_t total;\n  if mi_unlikely(mi_count_size_overflow(newcount, size, &total)) {\n    mi_try_new_handler(false);  // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc\n    return NULL;\n  }\n  else {\n    return mi_new_realloc(p, total);\n  }\n}\n\n#if MI_GUARDED\n// We always allocate a guarded allocation at an offset (`mi_page_has_aligned` will be true).\n// We then set the first word of the block to `0` for regular offset aligned allocations (in `alloc-aligned.c`)\n// and the first word to `~0` for guarded allocations to have a correct `mi_usable_size`\n\nstatic void* mi_block_ptr_set_guarded(mi_block_t* block, size_t obj_size) {\n  // TODO: we can still make padding work by moving it out of the guard page area\n  mi_page_t* const page = _mi_ptr_page(block);\n  mi_page_set_has_aligned(page, true);\n  block->next = MI_BLOCK_TAG_GUARDED;\n\n  // set guard page at the end of the block\n  mi_segment_t* const segment = _mi_page_segment(page);\n  const size_t block_size = mi_page_block_size(page);  // must use `block_size` to match `mi_free_local`\n  const size_t os_page_size = _mi_os_page_size();\n  mi_assert_internal(block_size >= obj_size + os_page_size + sizeof(mi_block_t));\n  if (block_size < obj_size + os_page_size + sizeof(mi_block_t)) {\n    // should never happen\n    mi_free(block);\n    return NULL;\n  }\n  uint8_t* guard_page = (uint8_t*)block + block_size - os_page_size;\n  mi_assert_internal(_mi_is_aligned(guard_page, os_page_size));\n  if mi_likely(segment->allow_decommit && _mi_is_aligned(guard_page, os_page_size)) {\n    const bool ok = _mi_os_protect(guard_page, os_page_size);\n    if mi_unlikely(!ok) {\n      _mi_warning_message(\"failed to set a guard page behind an object (object %p of size %zu)\\n\", block, block_size);\n    }\n  }\n  else {\n    _mi_warning_message(\"unable to set a guard page behind an object due to pinned memory (large OS pages?) (object %p of size %zu)\\n\", block, block_size);\n  }\n\n  // align pointer just in front of the guard page\n  size_t offset = block_size - os_page_size - obj_size;\n  mi_assert_internal(offset > sizeof(mi_block_t));\n  if (offset > MI_BLOCK_ALIGNMENT_MAX) {\n    // give up to place it right in front of the guard page if the offset is too large for unalignment\n    offset = MI_BLOCK_ALIGNMENT_MAX;\n  }\n  void* p = (uint8_t*)block + offset;\n  mi_track_align(block, p, offset, obj_size);\n  mi_track_mem_defined(block, sizeof(mi_block_t));\n  return p;\n}\n\nmi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept\n{\n  #if defined(MI_PADDING_SIZE)\n  mi_assert(MI_PADDING_SIZE==0);\n  #endif\n  // allocate multiple of page size ending in a guard page\n  // ensure minimal alignment requirement?\n  const size_t os_page_size = _mi_os_page_size();\n  const size_t obj_size = (mi_option_is_enabled(mi_option_guarded_precise) ? size : _mi_align_up(size, MI_MAX_ALIGN_SIZE));\n  const size_t bsize    = _mi_align_up(_mi_align_up(obj_size, MI_MAX_ALIGN_SIZE) + sizeof(mi_block_t), MI_MAX_ALIGN_SIZE);\n  const size_t req_size = _mi_align_up(bsize + os_page_size, os_page_size);\n  mi_block_t* const block = (mi_block_t*)_mi_malloc_generic(heap, req_size, zero, 0 /* huge_alignment */, NULL);\n  if (block==NULL) return NULL;\n  void* const p   = mi_block_ptr_set_guarded(block, obj_size);\n\n  // stats\n  mi_track_malloc(p, size, zero);\n  if (p != NULL) {\n    if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); }\n    #if MI_STAT>1\n    mi_heap_stat_adjust_decrease(heap, malloc_requested, req_size);\n    mi_heap_stat_increase(heap, malloc_requested, size);\n    #endif\n    _mi_stat_counter_increase(&heap->tld->stats.malloc_guarded_count, 1);\n  }\n  #if MI_DEBUG>3\n  if (p != NULL && zero) {\n    mi_assert_expensive(mi_mem_is_zero(p, size));\n  }\n  #endif\n  return p;\n}\n#endif\n\n// ------------------------------------------------------\n// ensure explicit external inline definitions are emitted!\n// ------------------------------------------------------\n\n#ifdef __cplusplus\nvoid* _mi_externs[] = {\n  (void*)&_mi_page_malloc,\n  (void*)&_mi_page_malloc_zero,\n  (void*)&_mi_heap_malloc_zero,\n  (void*)&_mi_heap_malloc_zero_ex,\n  (void*)&mi_malloc,\n  (void*)&mi_malloc_small,\n  (void*)&mi_zalloc_small,\n  (void*)&mi_heap_malloc,\n  (void*)&mi_heap_zalloc,\n  (void*)&mi_heap_malloc_small,\n  // (void*)&mi_heap_alloc_new,\n  // (void*)&mi_heap_alloc_new_n\n};\n#endif\n"
  },
  {
    "path": "src/arena-abandon.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#if !defined(MI_IN_ARENA_C)\n#error \"this file should be included from 'arena.c' (so mi_arena_t is visible)\"\n// add includes help an IDE\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"bitmap.h\"\n#endif\n\n// Minimal exports for arena-abandoned.\nsize_t      mi_arena_id_index(mi_arena_id_t id);\nmi_arena_t* mi_arena_from_index(size_t idx);\nsize_t      mi_arena_get_count(void);\nvoid*       mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex);\nbool        mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index);\n\n/* -----------------------------------------------------------\n  Abandoned blocks/segments:\n\n  _mi_arena_segment_clear_abandoned\n  _mi_arena_segment_mark_abandoned\n\n  This is used to atomically abandon/reclaim segments\n  (and crosses the arena API but it is convenient to have here).\n\n  Abandoned segments still have live blocks; they get reclaimed\n  when a thread frees a block in it, or when a thread needs a fresh\n  segment.\n\n  Abandoned segments are atomically marked in the `block_abandoned`\n  bitmap of arenas. Any segments allocated outside arenas are put\n  in the sub-process `abandoned_os_list`. This list is accessed\n  using locks but this should be uncommon and generally uncontended.\n  Reclaim and visiting either scan through the `block_abandoned`\n  bitmaps of the arena's, or visit the `abandoned_os_list`\n\n  A potentially nicer design is to use arena's for everything\n  and perhaps have virtual arena's to map OS allocated memory\n  but this would lack the \"density\" of our current arena's. TBC.\n----------------------------------------------------------- */\n\n\n// reclaim a specific OS abandoned segment; `true` on success.\n// sets the thread_id.\nstatic bool mi_arena_segment_os_clear_abandoned(mi_segment_t* segment, bool take_lock) {\n  mi_assert(segment->memid.memkind != MI_MEM_ARENA);\n  // not in an arena, remove from list of abandoned os segments\n  mi_subproc_t* const subproc = segment->subproc;\n  if (take_lock && !mi_lock_try_acquire(&subproc->abandoned_os_lock)) {\n    return false;  // failed to acquire the lock, we just give up\n  }\n  // remove atomically from the abandoned os list (if possible!)\n  bool reclaimed = false;\n  mi_segment_t* const next = segment->abandoned_os_next;\n  mi_segment_t* const prev = segment->abandoned_os_prev;\n  if (next != NULL || prev != NULL || subproc->abandoned_os_list == segment) {\n    #if MI_DEBUG>3\n    // find ourselves in the abandoned list (and check the count)\n    bool found = false;\n    size_t count = 0;\n    for (mi_segment_t* current = subproc->abandoned_os_list; current != NULL; current = current->abandoned_os_next) {\n      if (current == segment) { found = true; }\n      count++;\n    }\n    mi_assert_internal(found);\n    mi_assert_internal(count == mi_atomic_load_relaxed(&subproc->abandoned_os_list_count));\n    #endif\n    // remove (atomically) from the list and reclaim\n    if (prev != NULL) { prev->abandoned_os_next = next; }\n    else { subproc->abandoned_os_list = next; }\n    if (next != NULL) { next->abandoned_os_prev = prev; }\n    else { subproc->abandoned_os_list_tail = prev; }\n    segment->abandoned_os_next = NULL;\n    segment->abandoned_os_prev = NULL;\n    mi_atomic_decrement_relaxed(&subproc->abandoned_count);\n    mi_atomic_decrement_relaxed(&subproc->abandoned_os_list_count);\n    if (take_lock) { // don't reset the thread_id when iterating\n      mi_atomic_store_release(&segment->thread_id, _mi_thread_id());\n    }\n    reclaimed = true;\n  }\n  if (take_lock) { mi_lock_release(&segment->subproc->abandoned_os_lock); }\n  return reclaimed;\n}\n\n// reclaim a specific abandoned segment; `true` on success.\n// sets the thread_id.\nbool _mi_arena_segment_clear_abandoned(mi_segment_t* segment) {\n  if mi_unlikely(segment->memid.memkind != MI_MEM_ARENA) {\n    return mi_arena_segment_os_clear_abandoned(segment, true /* take lock */);\n  }\n  // arena segment: use the blocks_abandoned bitmap.\n  size_t arena_idx;\n  size_t bitmap_idx;\n  mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);\n  mi_arena_t* arena = mi_arena_from_index(arena_idx);\n  mi_assert_internal(arena != NULL);\n  // reclaim atomically\n  bool was_marked = _mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx);\n  if (was_marked) {\n    mi_assert_internal(mi_atomic_load_acquire(&segment->thread_id) == 0);\n    mi_atomic_decrement_relaxed(&segment->subproc->abandoned_count);\n    mi_atomic_store_release(&segment->thread_id, _mi_thread_id());\n  }\n  // mi_assert_internal(was_marked);\n  mi_assert_internal(!was_marked || _mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));\n  //mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));\n  return was_marked;\n}\n\n\n// mark a specific OS segment as abandoned\nstatic void mi_arena_segment_os_mark_abandoned(mi_segment_t* segment) {\n  mi_assert(segment->memid.memkind != MI_MEM_ARENA);\n  // not in an arena; we use a list of abandoned segments\n  mi_subproc_t* const subproc = segment->subproc;\n  mi_lock(&subproc->abandoned_os_lock) {\n    // push on the tail of the list (important for the visitor)\n    mi_segment_t* prev = subproc->abandoned_os_list_tail;\n    mi_assert_internal(prev == NULL || prev->abandoned_os_next == NULL);\n    mi_assert_internal(segment->abandoned_os_prev == NULL);\n    mi_assert_internal(segment->abandoned_os_next == NULL);\n    if (prev != NULL) { prev->abandoned_os_next = segment; }\n    else { subproc->abandoned_os_list = segment; }\n    subproc->abandoned_os_list_tail = segment;\n    segment->abandoned_os_prev = prev;\n    segment->abandoned_os_next = NULL;\n    mi_atomic_increment_relaxed(&subproc->abandoned_os_list_count);\n    mi_atomic_increment_relaxed(&subproc->abandoned_count);\n    // and release the lock\n  }\n  return;\n}\n\n// mark a specific segment as abandoned\n// clears the thread_id.\nvoid _mi_arena_segment_mark_abandoned(mi_segment_t* segment)\n{\n  mi_assert_internal(segment->used == segment->abandoned);\n  mi_atomic_store_release(&segment->thread_id, (uintptr_t)0);  // mark as abandoned for multi-thread free's\n  if mi_unlikely(segment->memid.memkind != MI_MEM_ARENA) {\n    mi_arena_segment_os_mark_abandoned(segment);\n    return;\n  }\n  // segment is in an arena, mark it in the arena `blocks_abandoned` bitmap\n  size_t arena_idx;\n  size_t bitmap_idx;\n  mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);\n  mi_arena_t* arena = mi_arena_from_index(arena_idx);\n  mi_assert_internal(arena != NULL);\n  // set abandonment atomically\n  mi_subproc_t* const subproc = segment->subproc; // don't access the segment after setting it abandoned\n  const bool was_unmarked = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);\n  if (was_unmarked) { mi_atomic_increment_relaxed(&subproc->abandoned_count); }\n  mi_assert_internal(was_unmarked);\n  mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));\n}\n\n\n/* -----------------------------------------------------------\n  Iterate through the abandoned blocks/segments using a cursor.\n  This is used for reclaiming and abandoned block visiting.\n----------------------------------------------------------- */\n\n// start a cursor at a randomized arena\nvoid _mi_arena_field_cursor_init(mi_heap_t* heap, mi_subproc_t* subproc, bool visit_all, mi_arena_field_cursor_t* current) {\n  mi_assert_internal(heap == NULL || heap->tld->segments.subproc == subproc);\n  current->bitmap_idx = 0;\n  current->subproc = subproc;\n  current->visit_all = visit_all;\n  current->hold_visit_lock = false;\n  const size_t abandoned_count = mi_atomic_load_relaxed(&subproc->abandoned_count);\n  const size_t abandoned_list_count = mi_atomic_load_relaxed(&subproc->abandoned_os_list_count);\n  const size_t max_arena = mi_arena_get_count();\n  if (heap != NULL && heap->arena_id != _mi_arena_id_none()) {\n    // for a heap that is bound to one arena, only visit that arena\n    current->start = mi_arena_id_index(heap->arena_id);\n    current->end = current->start + 1;\n    current->os_list_count = 0;\n  }\n  else {\n    // otherwise visit all starting at a random location\n    if (abandoned_count > abandoned_list_count && max_arena > 0) {\n      current->start = (heap == NULL || max_arena == 0 ? 0 : (mi_arena_id_t)(_mi_heap_random_next(heap) % max_arena));\n      current->end = current->start + max_arena;\n    }\n    else {\n      current->start = 0;\n      current->end = 0;\n    }\n    current->os_list_count = abandoned_list_count; // max entries to visit in the os abandoned list\n  }\n  mi_assert_internal(current->start <= max_arena);\n}\n\nvoid _mi_arena_field_cursor_done(mi_arena_field_cursor_t* current) {\n  if (current->hold_visit_lock) {\n    mi_lock_release(&current->subproc->abandoned_os_visit_lock);\n    current->hold_visit_lock = false;\n  }\n}\n\nstatic mi_segment_t* mi_arena_segment_clear_abandoned_at(mi_arena_t* arena, mi_subproc_t* subproc, mi_bitmap_index_t bitmap_idx) {\n  // try to reclaim an abandoned segment in the arena atomically\n  if (!_mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx)) return NULL;\n  mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));\n  mi_segment_t* segment = (mi_segment_t*)mi_arena_block_start(arena, bitmap_idx);\n  mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == 0);\n  // check that the segment belongs to our sub-process\n  // note: this is the reason we need the `abandoned_visit` lock in the case abandoned visiting is enabled.\n  //  without the lock an abandoned visit may otherwise fail to visit all abandoned segments in the sub-process.\n  //  for regular reclaim it is fine to miss one sometimes so without abandoned visiting we don't need the `abandoned_visit` lock.\n  if (segment->subproc != subproc) {\n    // it is from another sub-process, re-mark it and continue searching\n    const bool was_zero = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);\n    mi_assert_internal(was_zero); MI_UNUSED(was_zero);\n    return NULL;\n  }\n  else {\n    // success, we unabandoned a segment in our sub-process\n    mi_atomic_decrement_relaxed(&subproc->abandoned_count);\n    return segment;\n  }\n}\n\nstatic mi_segment_t* mi_arena_segment_clear_abandoned_next_field(mi_arena_field_cursor_t* previous) {\n  const size_t max_arena = mi_arena_get_count();\n  size_t field_idx = mi_bitmap_index_field(previous->bitmap_idx);\n  size_t bit_idx = mi_bitmap_index_bit_in_field(previous->bitmap_idx);\n  // visit arena's (from the previous cursor)\n  for (; previous->start < previous->end; previous->start++, field_idx = 0, bit_idx = 0) {\n    // index wraps around\n    size_t arena_idx = (previous->start >= max_arena ? previous->start % max_arena : previous->start);\n    mi_arena_t* arena = mi_arena_from_index(arena_idx);\n    if (arena != NULL) {\n      bool has_lock = false;\n      // visit the abandoned fields (starting at previous_idx)\n      for (; field_idx < arena->field_count; field_idx++, bit_idx = 0) {\n        size_t field = mi_atomic_load_relaxed(&arena->blocks_abandoned[field_idx]);\n        if mi_unlikely(field != 0) { // skip zero fields quickly\n          // we only take the arena lock if there are actually abandoned segments present\n          if (!has_lock && mi_option_is_enabled(mi_option_visit_abandoned)) {\n            has_lock = (previous->visit_all ? (mi_lock_acquire(&arena->abandoned_visit_lock),true) : mi_lock_try_acquire(&arena->abandoned_visit_lock));\n            if (!has_lock) {\n              if (previous->visit_all) {\n                _mi_error_message(EFAULT, \"internal error: failed to visit all abandoned segments due to failure to acquire the visitor lock\");\n              }\n              // skip to next arena\n              break;\n            }\n          }\n          mi_assert_internal(has_lock || !mi_option_is_enabled(mi_option_visit_abandoned));\n          // visit each set bit in the field  (todo: maybe use `ctz` here?)\n          for (; bit_idx < MI_BITMAP_FIELD_BITS; bit_idx++) {\n            // pre-check if the bit is set\n            size_t mask = ((size_t)1 << bit_idx);\n            if mi_unlikely((field & mask) == mask) {\n              mi_bitmap_index_t bitmap_idx = mi_bitmap_index_create(field_idx, bit_idx);\n              mi_segment_t* const segment = mi_arena_segment_clear_abandoned_at(arena, previous->subproc, bitmap_idx);\n              if (segment != NULL) {\n                //mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));\n                if (has_lock) { mi_lock_release(&arena->abandoned_visit_lock); }\n                previous->bitmap_idx = mi_bitmap_index_create_ex(field_idx, bit_idx + 1); // start at next one for the next iteration\n                return segment;\n              }\n            }\n          }\n        }\n      }\n      if (has_lock) { mi_lock_release(&arena->abandoned_visit_lock); }\n    }\n  }\n  return NULL;\n}\n\nstatic mi_segment_t* mi_arena_segment_clear_abandoned_next_list(mi_arena_field_cursor_t* previous) {\n  // go through the abandoned_os_list\n  // we only allow one thread per sub-process to do to visit guarded by the `abandoned_os_visit_lock`.\n  // The lock is released when the cursor is released.\n  if (!previous->hold_visit_lock) {\n    previous->hold_visit_lock = (previous->visit_all ? (mi_lock_acquire(&previous->subproc->abandoned_os_visit_lock),true)\n                                                     : mi_lock_try_acquire(&previous->subproc->abandoned_os_visit_lock));\n    if (!previous->hold_visit_lock) {\n      if (previous->visit_all) {\n        _mi_error_message(EFAULT, \"internal error: failed to visit all abandoned segments due to failure to acquire the OS visitor lock\");\n      }\n      return NULL; // we cannot get the lock, give up\n    }\n  }\n  // One list entry at a time\n  while (previous->os_list_count > 0) {\n    previous->os_list_count--;\n    mi_lock_acquire(&previous->subproc->abandoned_os_lock); // this could contend with concurrent OS block abandonment and reclaim from `free`\n    mi_segment_t* segment = previous->subproc->abandoned_os_list;\n    // pop from head of the list, a subsequent mark will push at the end (and thus we iterate through os_list_count entries)\n    if (segment == NULL || mi_arena_segment_os_clear_abandoned(segment, false /* we already have the lock */)) {\n      mi_lock_release(&previous->subproc->abandoned_os_lock);\n      return segment;\n    }\n    // already abandoned, try again\n    mi_lock_release(&previous->subproc->abandoned_os_lock);\n  }\n  // done\n  mi_assert_internal(previous->os_list_count == 0);\n  return NULL;\n}\n\n\n// reclaim abandoned segments\n// this does not set the thread id (so it appears as still abandoned)\nmi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_field_cursor_t* previous) {\n  if (previous->start < previous->end) {\n    // walk the arena\n    mi_segment_t* segment = mi_arena_segment_clear_abandoned_next_field(previous);\n    if (segment != NULL) { return segment; }\n  }\n  // no entries in the arena's anymore, walk the abandoned OS list\n  mi_assert_internal(previous->start == previous->end);\n  return mi_arena_segment_clear_abandoned_next_list(previous);\n}\n\n\nbool mi_abandoned_visit_blocks(mi_subproc_id_t subproc_id, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {\n  // (unfortunately) the visit_abandoned option must be enabled from the start.\n  // This is to avoid taking locks if abandoned list visiting is not required (as for most programs)\n  if (!mi_option_is_enabled(mi_option_visit_abandoned)) {\n    _mi_error_message(EFAULT, \"internal error: can only visit abandoned blocks when MIMALLOC_VISIT_ABANDONED=ON\");\n    return false;\n  }\n  mi_arena_field_cursor_t current;\n  _mi_arena_field_cursor_init(NULL, _mi_subproc_from_id(subproc_id), true /* visit all (blocking) */, &current);\n  mi_segment_t* segment;\n  bool ok = true;\n  while (ok && (segment = _mi_arena_segment_clear_abandoned_next(&current)) != NULL) {\n    ok = _mi_segment_visit_blocks(segment, heap_tag, visit_blocks, visitor, arg);\n    _mi_arena_segment_mark_abandoned(segment);\n  }\n  _mi_arena_field_cursor_done(&current);\n  return ok;\n}\n"
  },
  {
    "path": "src/arena.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* ----------------------------------------------------------------------------\n\"Arenas\" are fixed area's of OS memory from which we can allocate\nlarge blocks (>= MI_ARENA_MIN_BLOCK_SIZE, 4MiB).\nIn contrast to the rest of mimalloc, the arenas are shared between\nthreads and need to be accessed using atomic operations.\n\nArenas are also used to for huge OS page (1GiB) reservations or for reserving\nOS memory upfront which can be improve performance or is sometimes needed\non embedded devices. We can also employ this with WASI or `sbrk` systems\nto reserve large arenas upfront and be able to reuse the memory more effectively.\n\nThe arena allocation needs to be thread safe and we use an atomic bitmap to allocate.\n-----------------------------------------------------------------------------*/\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"bitmap.h\"\n\n\n/* -----------------------------------------------------------\n  Arena allocation\n----------------------------------------------------------- */\n\n// A memory arena descriptor\ntypedef struct mi_arena_s {\n  mi_arena_id_t       id;                   // arena id; 0 for non-specific\n  mi_memid_t          memid;                // memid of the memory area\n  _Atomic(uint8_t*)   start;                // the start of the memory area\n  size_t              block_count;          // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`)\n  size_t              field_count;          // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`)\n  size_t              meta_size;            // size of the arena structure itself (including its bitmaps)\n  mi_memid_t          meta_memid;           // memid of the arena structure itself (OS or static allocation)\n  int                 numa_node;            // associated NUMA node\n  bool                exclusive;            // only allow allocations if specifically for this arena\n  bool                is_large;             // memory area consists of large- or huge OS pages (always committed)\n  mi_lock_t           abandoned_visit_lock; // lock is only used when abandoned segments are being visited\n  _Atomic(size_t)     search_idx;           // optimization to start the search for free blocks\n  _Atomic(mi_msecs_t) purge_expire;         // expiration time when blocks should be purged from `blocks_purge`.\n\n  mi_bitmap_field_t*  blocks_dirty;         // are the blocks potentially non-zero?\n  mi_bitmap_field_t*  blocks_committed;     // are the blocks committed? (can be NULL for memory that cannot be decommitted)\n  mi_bitmap_field_t*  blocks_purge;         // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)\n  mi_bitmap_field_t*  blocks_abandoned;     // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)\n  mi_bitmap_field_t   blocks_inuse[1];      // in-place bitmap of in-use blocks (of size `field_count`)\n  // do not add further fields here as the dirty, committed, purged, and abandoned bitmaps follow the inuse bitmap fields.\n} mi_arena_t;\n\n\n#define MI_ARENA_BLOCK_SIZE   (MI_SEGMENT_SIZE)        // 64MiB  (must be at least MI_SEGMENT_ALIGN)\n#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2)  // 32MiB\n#define MI_MAX_ARENAS         (132)                    // Limited as the reservation exponentially increases (and takes up .bss)\n\n// The available arenas\nstatic mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS];\nstatic mi_decl_cache_align _Atomic(size_t)      mi_arena_count; // = 0\nstatic mi_decl_cache_align _Atomic(int64_t)     mi_arenas_purge_expire; // set if there exist purgeable arenas\n\n#define MI_IN_ARENA_C\n#include \"arena-abandon.c\"\n#undef MI_IN_ARENA_C\n\n/* -----------------------------------------------------------\n  Arena id's\n  id = arena_index + 1\n----------------------------------------------------------- */\n\nsize_t mi_arena_id_index(mi_arena_id_t id) {\n  return (size_t)(id <= 0 ? MI_MAX_ARENAS : id - 1);\n}\n\nstatic mi_arena_id_t mi_arena_id_create(size_t arena_index) {\n  mi_assert_internal(arena_index < MI_MAX_ARENAS);\n  return (int)arena_index + 1;\n}\n\nmi_arena_id_t _mi_arena_id_none(void) {\n  return 0;\n}\n\nstatic bool mi_arena_id_is_suitable(mi_arena_id_t arena_id, bool arena_is_exclusive, mi_arena_id_t req_arena_id) {\n  return ((!arena_is_exclusive && req_arena_id == _mi_arena_id_none()) ||\n          (arena_id == req_arena_id));\n}\n\nbool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id) {\n  if (memid.memkind == MI_MEM_ARENA) {\n    return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id);\n  }\n  else {\n    return mi_arena_id_is_suitable(_mi_arena_id_none(), false, request_arena_id);\n  }\n}\n\nbool _mi_arena_memid_is_os_allocated(mi_memid_t memid) {\n  return (memid.memkind == MI_MEM_OS);\n}\n\nsize_t mi_arena_get_count(void) {\n  return mi_atomic_load_relaxed(&mi_arena_count);\n}\n\nmi_arena_t* mi_arena_from_index(size_t idx) {\n  mi_assert_internal(idx < mi_arena_get_count());\n  return mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[idx]);\n}\n\n\n/* -----------------------------------------------------------\n  Arena allocations get a (currently) 16-bit memory id where the\n  lower 8 bits are the arena id, and the upper bits the block index.\n----------------------------------------------------------- */\n\nstatic size_t mi_block_count_of_size(size_t size) {\n  return _mi_divide_up(size, MI_ARENA_BLOCK_SIZE);\n}\n\nstatic size_t mi_arena_block_size(size_t bcount) {\n  return (bcount * MI_ARENA_BLOCK_SIZE);\n}\n\nstatic size_t mi_arena_size(mi_arena_t* arena) {\n  return mi_arena_block_size(arena->block_count);\n}\n\nstatic mi_memid_t mi_memid_create_arena(mi_arena_id_t id, bool is_exclusive, mi_bitmap_index_t bitmap_index) {\n  mi_memid_t memid = _mi_memid_create(MI_MEM_ARENA);\n  memid.mem.arena.id = id;\n  memid.mem.arena.block_index = bitmap_index;\n  memid.mem.arena.is_exclusive = is_exclusive;\n  return memid;\n}\n\nbool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) {\n  mi_assert_internal(memid.memkind == MI_MEM_ARENA);\n  *arena_index = mi_arena_id_index(memid.mem.arena.id);\n  *bitmap_index = memid.mem.arena.block_index;\n  return memid.mem.arena.is_exclusive;\n}\n\n\n\n/* -----------------------------------------------------------\n  Special static area for mimalloc internal structures\n  to avoid OS calls (for example, for the arena metadata (~= 256b))\n----------------------------------------------------------- */\n\n#define MI_ARENA_STATIC_MAX  ((MI_INTPTR_SIZE/2)*MI_KiB)  // 4 KiB on 64-bit\n\nstatic mi_decl_cache_align uint8_t mi_arena_static[MI_ARENA_STATIC_MAX];  // must be cache aligned, see issue #895\nstatic mi_decl_cache_align _Atomic(size_t) mi_arena_static_top;\n\nstatic void* mi_arena_static_zalloc(size_t size, size_t alignment, mi_memid_t* memid) {\n  *memid = _mi_memid_none();\n  if (size == 0 || size > MI_ARENA_STATIC_MAX) return NULL;\n  const size_t toplow = mi_atomic_load_relaxed(&mi_arena_static_top);\n  if ((toplow + size) > MI_ARENA_STATIC_MAX) return NULL;\n\n  // try to claim space\n  if (alignment < MI_MAX_ALIGN_SIZE) { alignment = MI_MAX_ALIGN_SIZE; }\n  const size_t oversize = size + alignment - 1;\n  if (toplow + oversize > MI_ARENA_STATIC_MAX) return NULL;\n  const size_t oldtop = mi_atomic_add_acq_rel(&mi_arena_static_top, oversize);\n  size_t top = oldtop + oversize;\n  if (top > MI_ARENA_STATIC_MAX) {\n    // try to roll back, ok if this fails\n    mi_atomic_cas_strong_acq_rel(&mi_arena_static_top, &top, oldtop);\n    return NULL;\n  }\n\n  // success\n  *memid = _mi_memid_create(MI_MEM_STATIC);\n  memid->initially_zero = true;\n  const size_t start = _mi_align_up(oldtop, alignment);\n  uint8_t* const p = &mi_arena_static[start];\n  _mi_memzero_aligned(p, size);\n  return p;\n}\n\nvoid* _mi_arena_meta_zalloc(size_t size, mi_memid_t* memid) {\n  *memid = _mi_memid_none();\n\n  // try static\n  void* p = mi_arena_static_zalloc(size, MI_MAX_ALIGN_SIZE, memid);\n  if (p != NULL) return p;\n\n  // or fall back to the OS\n  p = _mi_os_zalloc(size, memid);\n  if (p == NULL) return NULL;\n\n  return p;\n}\n\nvoid _mi_arena_meta_free(void* p, mi_memid_t memid, size_t size) {\n  if (mi_memkind_is_os(memid.memkind)) {\n    _mi_os_free(p, size, memid);\n  }\n  else {\n    mi_assert(memid.memkind == MI_MEM_STATIC);\n  }\n}\n\nvoid* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex) {\n  return (arena->start + mi_arena_block_size(mi_bitmap_index_bit(bindex)));\n}\n\n\n/* -----------------------------------------------------------\n  Thread safe allocation in an arena\n----------------------------------------------------------- */\n\n// claim the `blocks_inuse` bits\nstatic bool mi_arena_try_claim(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t* bitmap_idx)\n{\n  size_t idx = 0; // mi_atomic_load_relaxed(&arena->search_idx);  // start from last search; ok to be relaxed as the exact start does not matter\n  if (_mi_bitmap_try_find_from_claim_across(arena->blocks_inuse, arena->field_count, idx, blocks, bitmap_idx)) {\n    mi_atomic_store_relaxed(&arena->search_idx, mi_bitmap_index_field(*bitmap_idx));  // start search from found location next time around\n    return true;\n  };\n  return false;\n}\n\n\n/* -----------------------------------------------------------\n  Arena Allocation\n----------------------------------------------------------- */\n\nstatic mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t arena_index, size_t needed_bcount,\n                                                    bool commit, mi_memid_t* memid)\n{\n  MI_UNUSED(arena_index);\n  mi_assert_internal(mi_arena_id_index(arena->id) == arena_index);\n\n  mi_bitmap_index_t bitmap_index;\n  if (!mi_arena_try_claim(arena, needed_bcount, &bitmap_index)) return NULL;\n\n  // claimed it!\n  void* p = mi_arena_block_start(arena, bitmap_index);\n  *memid = mi_memid_create_arena(arena->id, arena->exclusive, bitmap_index);\n  memid->is_pinned = arena->memid.is_pinned;\n\n  // none of the claimed blocks should be scheduled for a decommit\n  if (arena->blocks_purge != NULL) {\n    // this is thread safe as a potential purge only decommits parts that are not yet claimed as used (in `blocks_inuse`).\n    _mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, needed_bcount, bitmap_index);\n  }\n\n  // set the dirty bits (todo: no need for an atomic op here?)\n  if (arena->memid.initially_zero && arena->blocks_dirty != NULL) {\n    memid->initially_zero = _mi_bitmap_claim_across(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL, NULL);\n  }\n\n  // set commit state\n  if (arena->blocks_committed == NULL) {\n    // always committed\n    memid->initially_committed = true;\n  }\n  else if (commit) {\n    // commit requested, but the range may not be committed as a whole: ensure it is committed now\n    memid->initially_committed = true;\n    const size_t commit_size = mi_arena_block_size(needed_bcount);      \n    bool any_uncommitted;\n    size_t already_committed = 0;\n    _mi_bitmap_claim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted, &already_committed);\n    if (any_uncommitted) {\n      mi_assert_internal(already_committed < needed_bcount);\n      const size_t stat_commit_size = commit_size - mi_arena_block_size(already_committed);\n      bool commit_zero = false;\n      if (!_mi_os_commit_ex(p, commit_size, &commit_zero, stat_commit_size)) {\n        memid->initially_committed = false;\n      }\n      else {\n        if (commit_zero) { memid->initially_zero = true; }\n      }\n    }\n    else {\n      // all are already committed: signal that we are reusing memory in case it was purged before\n      _mi_os_reuse( p, commit_size );\n    }\n  }\n  else {\n    // no need to commit, but check if already fully committed\n    size_t already_committed = 0;\n    memid->initially_committed = _mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &already_committed);\n    if (!memid->initially_committed && already_committed > 0) {\n      // partially committed: as it will be committed at some time, adjust the stats and pretend the range is fully uncommitted.\n      mi_assert_internal(already_committed < needed_bcount);\n      _mi_stat_decrease(&_mi_stats_main.committed, mi_arena_block_size(already_committed));\n      _mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index);\n    }\n  }\n\n  return p;\n}\n\n// allocate in a specific arena\nstatic void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_node, int numa_node, size_t size, size_t alignment,\n                                       bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid )\n{\n  MI_UNUSED_RELEASE(alignment);\n  mi_assert(alignment <= MI_SEGMENT_ALIGN);\n  const size_t bcount = mi_block_count_of_size(size);\n  const size_t arena_index = mi_arena_id_index(arena_id);\n  mi_assert_internal(arena_index < mi_atomic_load_relaxed(&mi_arena_count));\n  mi_assert_internal(size <= mi_arena_block_size(bcount));\n\n  // Check arena suitability\n  mi_arena_t* arena = mi_arena_from_index(arena_index);\n  if (arena == NULL) return NULL;\n  if (!allow_large && arena->is_large) return NULL;\n  if (!mi_arena_id_is_suitable(arena->id, arena->exclusive, req_arena_id)) return NULL;\n  if (req_arena_id == _mi_arena_id_none()) { // in not specific, check numa affinity\n    const bool numa_suitable = (numa_node < 0 || arena->numa_node < 0 || arena->numa_node == numa_node);\n    if (match_numa_node) { if (!numa_suitable) return NULL; }\n                    else { if (numa_suitable) return NULL; }\n  }\n\n  // try to allocate\n  void* p = mi_arena_try_alloc_at(arena, arena_index, bcount, commit, memid);\n  mi_assert_internal(p == NULL || _mi_is_aligned(p, alignment));\n  return p;\n}\n\n\n// allocate from an arena with fallback to the OS\nstatic mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, size_t alignment,\n                                                  bool commit, bool allow_large,\n                                                  mi_arena_id_t req_arena_id, mi_memid_t* memid )\n{\n  MI_UNUSED(alignment);\n  mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);\n  const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);\n  if mi_likely(max_arena == 0) return NULL;\n\n  if (req_arena_id != _mi_arena_id_none()) {\n    // try a specific arena if requested\n    if (mi_arena_id_index(req_arena_id) < max_arena) {\n      void* p = mi_arena_try_alloc_at_id(req_arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);\n      if (p != NULL) return p;\n    }\n  }\n  else {\n    // try numa affine allocation\n    for (size_t i = 0; i < max_arena; i++) {\n      void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);\n      if (p != NULL) return p;\n    }\n\n    // try from another numa node instead..\n    if (numa_node >= 0) {  // if numa_node was < 0 (no specific affinity requested), all arena's have been tried already\n      for (size_t i = 0; i < max_arena; i++) {\n        void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), false /* only proceed if not numa local */, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);\n        if (p != NULL) return p;\n      }\n    }\n  }\n  return NULL;\n}\n\n// try to reserve a fresh arena space\nstatic bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t *arena_id)\n{\n  if (_mi_preloading()) return false;  // use OS only while pre loading\n\n  const size_t arena_count = mi_atomic_load_acquire(&mi_arena_count);\n  if (arena_count > (MI_MAX_ARENAS - 4)) return false;\n\n  size_t arena_reserve = mi_option_get_size(mi_option_arena_reserve);\n  if (arena_reserve == 0) return false;\n\n  if (!_mi_os_has_virtual_reserve()) {\n    arena_reserve = arena_reserve/4;  // be conservative if virtual reserve is not supported (for WASM for example)\n  }\n  arena_reserve = _mi_align_up(arena_reserve, MI_ARENA_BLOCK_SIZE);\n  arena_reserve = _mi_align_up(arena_reserve, MI_SEGMENT_SIZE);\n  if (arena_count >= 8 && arena_count <= 128) {\n    // scale up the arena sizes exponentially every 8 entries (128 entries get to 589TiB)\n    const size_t multiplier = (size_t)1 << _mi_clamp(arena_count/8, 0, 16 );\n    size_t reserve = 0;\n    if (!mi_mul_overflow(multiplier, arena_reserve, &reserve)) {\n      arena_reserve = reserve;\n    }\n  }\n  if (arena_reserve < req_size) return false;  // should be able to at least handle the current allocation size\n\n  // commit eagerly?\n  bool arena_commit = false;\n  if (mi_option_get(mi_option_arena_eager_commit) == 2)      { arena_commit = _mi_os_has_overcommit(); }\n  else if (mi_option_get(mi_option_arena_eager_commit) == 1) { arena_commit = true; }\n\n  return (mi_reserve_os_memory_ex(arena_reserve, arena_commit, allow_large, false /* exclusive? */, arena_id) == 0);\n}\n\n\nvoid* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large,\n                              mi_arena_id_t req_arena_id, mi_memid_t* memid)\n{\n  mi_assert_internal(memid != NULL);\n  mi_assert_internal(size > 0);\n  *memid = _mi_memid_none();\n\n  const int numa_node = _mi_os_numa_node(); // current numa node\n\n  // try to allocate in an arena if the alignment is small enough and the object is not too small (as for heap meta data)\n  if (!mi_option_is_enabled(mi_option_disallow_arena_alloc)) {  // is arena allocation allowed?\n    if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN && align_offset == 0)\n    {\n      void* p = mi_arena_try_alloc(numa_node, size, alignment, commit, allow_large, req_arena_id, memid);\n      if (p != NULL) return p;\n\n      // otherwise, try to first eagerly reserve a new arena\n      if (req_arena_id == _mi_arena_id_none()) {\n        mi_arena_id_t arena_id = 0;\n        if (mi_arena_reserve(size, allow_large, &arena_id)) {\n          // and try allocate in there\n          mi_assert_internal(req_arena_id == _mi_arena_id_none());\n          p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);\n          if (p != NULL) return p;\n        }\n      }\n    }\n  }\n\n  // if we cannot use OS allocation, return NULL\n  if (mi_option_is_enabled(mi_option_disallow_os_alloc) || req_arena_id != _mi_arena_id_none()) {\n    errno = ENOMEM;\n    return NULL;\n  }\n\n  // finally, fall back to the OS\n  if (align_offset > 0) {\n    return _mi_os_alloc_aligned_at_offset(size, alignment, align_offset, commit, allow_large, memid);\n  }\n  else {\n    return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid);\n  }\n}\n\nvoid* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid)\n{\n  return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, 0, commit, allow_large, req_arena_id, memid);\n}\n\n\nvoid* mi_arena_area(mi_arena_id_t arena_id, size_t* size) {\n  if (size != NULL) *size = 0;\n  size_t arena_index = mi_arena_id_index(arena_id);\n  if (arena_index >= MI_MAX_ARENAS) return NULL;\n  mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]);\n  if (arena == NULL) return NULL;\n  if (size != NULL) { *size = mi_arena_block_size(arena->block_count); }\n  return arena->start;\n}\n\n\n/* -----------------------------------------------------------\n  Arena purge\n----------------------------------------------------------- */\n\nstatic long mi_arena_purge_delay(void) {\n  // <0 = no purging allowed, 0=immediate purging, >0=milli-second delay\n  return (mi_option_get(mi_option_purge_delay) * mi_option_get(mi_option_arena_purge_mult));\n}\n\n// reset or decommit in an arena and update the committed/decommit bitmaps\n// assumes we own the area (i.e. blocks_in_use is claimed by us)\nstatic void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks) {\n  mi_assert_internal(arena->blocks_committed != NULL);\n  mi_assert_internal(arena->blocks_purge != NULL);\n  mi_assert_internal(!arena->memid.is_pinned);\n  const size_t size = mi_arena_block_size(blocks);\n  void* const p = mi_arena_block_start(arena, bitmap_idx);\n  bool needs_recommit;\n  size_t already_committed = 0;\n  if (_mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx, &already_committed)) {\n    // all blocks are committed, we can purge freely\n    mi_assert_internal(already_committed == blocks);\n    needs_recommit = _mi_os_purge(p, size);\n  }\n  else {\n    // some blocks are not committed -- this can happen when a partially committed block is freed\n    // in `_mi_arena_free` and it is conservatively marked as uncommitted but still scheduled for a purge\n    // we need to ensure we do not try to reset (as that may be invalid for uncommitted memory).\n    mi_assert_internal(already_committed < blocks);\n    mi_assert_internal(mi_option_is_enabled(mi_option_purge_decommits));\n    needs_recommit = _mi_os_purge_ex(p, size, false /* allow reset? */, mi_arena_block_size(already_committed));\n  }\n\n  // clear the purged blocks\n  _mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx);\n  // update committed bitmap\n  if (needs_recommit) {\n    _mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);\n  }\n}\n\n// Schedule a purge. This is usually delayed to avoid repeated decommit/commit calls.\n// Note: assumes we (still) own the area as we may purge immediately\nstatic void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks) {\n  mi_assert_internal(arena->blocks_purge != NULL);\n  const long delay = mi_arena_purge_delay();\n  if (delay < 0) return;  // is purging allowed at all?\n\n  if (_mi_preloading() || delay == 0) {\n    // decommit directly\n    mi_arena_purge(arena, bitmap_idx, blocks);\n  }\n  else {\n    // schedule purge\n    const mi_msecs_t expire = _mi_clock_now() + delay;\n    mi_msecs_t expire0 = 0;\n    if (mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire0, expire)) {\n      // expiration was not yet set\n      // maybe set the global arenas expire as well (if it wasn't set already)\n      mi_atomic_casi64_strong_acq_rel(&mi_arenas_purge_expire, &expire0, expire);\n    }\n    else {\n      // already an expiration was set\n    }\n    _mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL, NULL);\n  }\n}\n\n// purge a range of blocks\n// return true if the full range was purged.\n// assumes we own the area (i.e. blocks_in_use is claimed by us)\nstatic bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx, size_t bitlen, size_t purge) {\n  const size_t endidx = startidx + bitlen;\n  size_t bitidx = startidx;\n  bool all_purged = false;\n  while (bitidx < endidx) {\n    // count consecutive ones in the purge mask\n    size_t count = 0;\n    while (bitidx + count < endidx && (purge & ((size_t)1 << (bitidx + count))) != 0) {\n      count++;\n    }\n    if (count > 0) {\n      // found range to be purged\n      const mi_bitmap_index_t range_idx = mi_bitmap_index_create(idx, bitidx);\n      mi_arena_purge(arena, range_idx, count);\n      if (count == bitlen) {\n        all_purged = true;\n      }\n    }\n    bitidx += (count+1); // +1 to skip the zero bit (or end)\n  }\n  return all_purged;\n}\n\n// returns true if anything was purged\nstatic bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force)\n{\n  // check pre-conditions\n  if (arena->memid.is_pinned) return false;\n\n  // expired yet?\n  mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);\n  if (!force && (expire == 0 || expire > now)) return false;\n\n  // reset expire (if not already set concurrently)\n  mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, (mi_msecs_t)0);\n  _mi_stat_counter_increase(&_mi_stats_main.arena_purges, 1);\n\n  // potential purges scheduled, walk through the bitmap\n  bool any_purged = false;\n  bool full_purge = true;\n  for (size_t i = 0; i < arena->field_count; i++) {\n    size_t purge = mi_atomic_load_relaxed(&arena->blocks_purge[i]);\n    if (purge != 0) {\n      size_t bitidx = 0;\n      while (bitidx < MI_BITMAP_FIELD_BITS) {\n        // find consecutive range of ones in the purge mask\n        size_t bitlen = 0;\n        while (bitidx + bitlen < MI_BITMAP_FIELD_BITS && (purge & ((size_t)1 << (bitidx + bitlen))) != 0) {\n          bitlen++;\n        }\n        // temporarily claim the purge range as \"in-use\" to be thread-safe with allocation\n        // try to claim the longest range of corresponding in_use bits\n        const mi_bitmap_index_t bitmap_index = mi_bitmap_index_create(i, bitidx);\n        while( bitlen > 0 ) {\n          if (_mi_bitmap_try_claim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index)) {\n            break;\n          }\n          bitlen--;\n        }\n        // actual claimed bits at `in_use`\n        if (bitlen > 0) {\n          // read purge again now that we have the in_use bits\n          purge = mi_atomic_load_acquire(&arena->blocks_purge[i]);\n          if (!mi_arena_purge_range(arena, i, bitidx, bitlen, purge)) {\n            full_purge = false;\n          }\n          any_purged = true;\n          // release the claimed `in_use` bits again\n          _mi_bitmap_unclaim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index);\n        }\n        bitidx += (bitlen+1);  // +1 to skip the zero (or end)\n      } // while bitidx\n    } // purge != 0\n  }\n  // if not fully purged, make sure to purge again in the future\n  if (!full_purge) {\n    const long delay = mi_arena_purge_delay();\n    mi_msecs_t expected = 0;\n    mi_atomic_casi64_strong_acq_rel(&arena->purge_expire,&expected,_mi_clock_now() + delay);\n  }\n  return any_purged;\n}\n\nstatic void mi_arenas_try_purge( bool force, bool visit_all )\n{\n  if (_mi_preloading() || mi_arena_purge_delay() <= 0) return;  // nothing will be scheduled\n\n  // check if any arena needs purging?\n  const mi_msecs_t now = _mi_clock_now();\n  mi_msecs_t arenas_expire = mi_atomic_loadi64_acquire(&mi_arenas_purge_expire);\n  if (!force && (arenas_expire == 0 || arenas_expire < now)) return;\n\n  const size_t max_arena = mi_atomic_load_acquire(&mi_arena_count);\n  if (max_arena == 0) return;\n\n  // allow only one thread to purge at a time\n  static mi_atomic_guard_t purge_guard;\n  mi_atomic_guard(&purge_guard)\n  {\n    // increase global expire: at most one purge per delay cycle\n    mi_atomic_storei64_release(&mi_arenas_purge_expire, now + mi_arena_purge_delay());\n    size_t max_purge_count = (visit_all ? max_arena : 2);\n    bool all_visited = true;\n    for (size_t i = 0; i < max_arena; i++) {\n      mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);\n      if (arena != NULL) {\n        if (mi_arena_try_purge(arena, now, force)) {\n          if (max_purge_count <= 1) {\n            all_visited = false;\n            break;\n          }\n          max_purge_count--;\n        }\n      }\n    }\n    if (all_visited) {\n      // all arena's were visited and purged: reset global expire\n      mi_atomic_storei64_release(&mi_arenas_purge_expire, 0);\n    }\n  }\n}\n\n\n/* -----------------------------------------------------------\n  Arena free\n----------------------------------------------------------- */\n\nvoid _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memid) {\n  mi_assert_internal(size > 0);\n  mi_assert_internal(committed_size <= size);\n  if (p==NULL) return;\n  if (size==0) return;\n  const bool all_committed = (committed_size == size);\n  const size_t decommitted_size = (committed_size <= size ? size - committed_size : 0);\n\n  // need to set all memory to undefined as some parts may still be marked as no_access (like padding etc.)\n  mi_track_mem_undefined(p,size);\n\n  if (mi_memkind_is_os(memid.memkind)) {\n    // was a direct OS allocation, pass through\n    if (!all_committed && decommitted_size > 0) {\n      // if partially committed, adjust the committed stats (as `_mi_os_free` will decrease commit by the full size)\n      _mi_stat_increase(&_mi_stats_main.committed, decommitted_size);\n    }\n    _mi_os_free(p, size, memid);\n  }\n  else if (memid.memkind == MI_MEM_ARENA) {\n    // allocated in an arena\n    size_t arena_idx;\n    size_t bitmap_idx;\n    mi_arena_memid_indices(memid, &arena_idx, &bitmap_idx);\n    mi_assert_internal(arena_idx < MI_MAX_ARENAS);\n    mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t,&mi_arenas[arena_idx]);\n    mi_assert_internal(arena != NULL);\n    const size_t blocks = mi_block_count_of_size(size);\n\n    // checks\n    if (arena == NULL) {\n      _mi_error_message(EINVAL, \"trying to free from an invalid arena: %p, size %zu, memid: 0x%zx\\n\", p, size, memid);\n      return;\n    }\n    mi_assert_internal(arena->field_count > mi_bitmap_index_field(bitmap_idx));\n    if (arena->field_count <= mi_bitmap_index_field(bitmap_idx)) {\n      _mi_error_message(EINVAL, \"trying to free from an invalid arena block: %p, size %zu, memid: 0x%zx\\n\", p, size, memid);\n      return;\n    }\n\n    // potentially decommit\n    if (arena->memid.is_pinned || arena->blocks_committed == NULL) {\n      mi_assert_internal(all_committed);\n    }\n    else {\n      mi_assert_internal(arena->blocks_committed != NULL);\n      mi_assert_internal(arena->blocks_purge != NULL);\n\n      if (!all_committed) {\n        // mark the entire range as no longer committed (so we will recommit the full range when re-using)\n        _mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);\n        mi_track_mem_noaccess(p,size);\n        //if (committed_size > 0) {\n          // if partially committed, adjust the committed stats (is it will be recommitted when re-using)\n          // in the delayed purge, we do no longer decrease the commit if the range is not marked entirely as committed.\n          _mi_stat_decrease(&_mi_stats_main.committed, committed_size);\n        //}\n        // note: if not all committed, it may be that the purge will reset/decommit the entire range\n        // that contains already decommitted parts. Since purge consistently uses reset or decommit that\n        // works (as we should never reset decommitted parts).\n      }\n      // (delay) purge the entire range\n      mi_arena_schedule_purge(arena, bitmap_idx, blocks);\n    }\n\n    // and make it available to others again\n    bool all_inuse = _mi_bitmap_unclaim_across(arena->blocks_inuse, arena->field_count, blocks, bitmap_idx);\n    if (!all_inuse) {\n      _mi_error_message(EAGAIN, \"trying to free an already freed arena block: %p, size %zu\\n\", p, size);\n      return;\n    };\n  }\n  else {\n    // arena was none, external, or static; nothing to do\n    mi_assert_internal(memid.memkind < MI_MEM_OS);\n  }\n\n  // purge expired decommits\n  mi_arenas_try_purge(false, false);\n}\n\n// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`\n// for dynamic libraries that are unloaded and need to release all their allocated memory.\nstatic void mi_arenas_unsafe_destroy(void) {\n  const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);\n  size_t new_max_arena = 0;\n  for (size_t i = 0; i < max_arena; i++) {\n    mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);\n    if (arena != NULL) {\n      mi_lock_done(&arena->abandoned_visit_lock);\n      if (arena->start != NULL && mi_memkind_is_os(arena->memid.memkind)) {\n        mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], NULL);\n        _mi_os_free(arena->start, mi_arena_size(arena), arena->memid);\n      }\n      else {\n        new_max_arena = i;\n      }\n      _mi_arena_meta_free(arena, arena->meta_memid, arena->meta_size);\n    }\n  }\n\n  // try to lower the max arena.\n  size_t expected = max_arena;\n  mi_atomic_cas_strong_acq_rel(&mi_arena_count, &expected, new_max_arena);\n}\n\n// Purge the arenas; if `force_purge` is true, amenable parts are purged even if not yet expired\nvoid _mi_arenas_collect(bool force_purge) {\n  mi_arenas_try_purge(force_purge, force_purge /* visit all? */);\n}\n\n// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`\n// for dynamic libraries that are unloaded and need to release all their allocated memory.\nvoid _mi_arena_unsafe_destroy_all(void) {\n  mi_arenas_unsafe_destroy();\n  _mi_arenas_collect(true /* force purge */);  // purge non-owned arenas\n}\n\n// Is a pointer inside any of our arenas?\nbool _mi_arena_contains(const void* p) {\n  const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);\n  for (size_t i = 0; i < max_arena; i++) {\n    mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);\n    if (arena != NULL && arena->start <= (const uint8_t*)p && arena->start + mi_arena_block_size(arena->block_count) > (const uint8_t*)p) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/* -----------------------------------------------------------\n  Add an arena.\n----------------------------------------------------------- */\n\nstatic bool mi_arena_add(mi_arena_t* arena, mi_arena_id_t* arena_id, mi_stats_t* stats) {\n  mi_assert_internal(arena != NULL);\n  mi_assert_internal((uintptr_t)mi_atomic_load_ptr_relaxed(uint8_t,&arena->start) % MI_SEGMENT_ALIGN == 0);\n  mi_assert_internal(arena->block_count > 0);\n  if (arena_id != NULL) { *arena_id = -1; }\n\n  size_t i = mi_atomic_load_relaxed(&mi_arena_count);\n  while (i < MI_MAX_ARENAS) {\n    if (mi_atomic_cas_strong_acq_rel(&mi_arena_count, &i, i+1)) {\n      _mi_stat_counter_increase(&stats->arena_count, 1);\n      arena->id = mi_arena_id_create(i);\n      mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], arena);\n      if (arena_id != NULL) { *arena_id = arena->id; }\n      return true;\n    }\n  }\n\n  return false;\n}\n\nstatic bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept\n{\n  if (arena_id != NULL) *arena_id = _mi_arena_id_none();\n  if (size < MI_ARENA_BLOCK_SIZE) {\n    _mi_warning_message(\"the arena size is too small (memory at %p with size %zu)\\n\", start, size);\n    return false;\n  }\n  if (is_large) {\n    mi_assert_internal(memid.initially_committed && memid.is_pinned);\n  }\n  if (!_mi_is_aligned(start, MI_SEGMENT_ALIGN)) {\n    void* const aligned_start = mi_align_up_ptr(start, MI_SEGMENT_ALIGN);\n    const size_t diff = (uint8_t*)aligned_start - (uint8_t*)start;\n    if (diff >= size || (size - diff) < MI_ARENA_BLOCK_SIZE) {\n      _mi_warning_message(\"after alignment, the size of the arena becomes too small (memory at %p with size %zu)\\n\", start, size);\n      return false;\n    }\n    start = aligned_start;\n    size = size - diff;\n  }\n\n  const size_t bcount = size / MI_ARENA_BLOCK_SIZE;\n  const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS);\n  const size_t bitmaps = (memid.is_pinned ? 3 : 5);\n  const size_t asize  = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t));\n  mi_memid_t meta_memid;\n  mi_arena_t* arena   = (mi_arena_t*)_mi_arena_meta_zalloc(asize, &meta_memid);\n  if (arena == NULL) return false;\n\n  // already zero'd due to zalloc\n  // _mi_memzero(arena, asize);\n  arena->id = _mi_arena_id_none();\n  arena->memid = memid;\n  arena->exclusive = exclusive;\n  arena->meta_size = asize;\n  arena->meta_memid = meta_memid;\n  arena->block_count = bcount;\n  arena->field_count = fields;\n  arena->start = (uint8_t*)start;\n  arena->numa_node    = numa_node; // TODO: or get the current numa node if -1? (now it allows anyone to allocate on -1)\n  arena->is_large     = is_large;\n  arena->purge_expire = 0;\n  arena->search_idx   = 0;\n  mi_lock_init(&arena->abandoned_visit_lock);\n  // consecutive bitmaps\n  arena->blocks_dirty     = &arena->blocks_inuse[fields];     // just after inuse bitmap\n  arena->blocks_abandoned = &arena->blocks_inuse[2 * fields]; // just after dirty bitmap\n  arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after abandoned bitmap\n  arena->blocks_purge     = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[4*fields]); // just after committed bitmap\n  // initialize committed bitmap?\n  if (arena->blocks_committed != NULL && arena->memid.initially_committed) {\n    memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning\n  }\n\n  // and claim leftover blocks if needed (so we never allocate there)\n  ptrdiff_t post = (fields * MI_BITMAP_FIELD_BITS) - bcount;\n  mi_assert_internal(post >= 0);\n  if (post > 0) {\n    // don't use leftover bits at the end\n    mi_bitmap_index_t postidx = mi_bitmap_index_create(fields - 1, MI_BITMAP_FIELD_BITS - post);\n    _mi_bitmap_claim(arena->blocks_inuse, fields, post, postidx, NULL);\n  }\n  return mi_arena_add(arena, arena_id, &_mi_stats_main);\n\n}\n\nbool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {\n  mi_memid_t memid = _mi_memid_create(MI_MEM_EXTERNAL);\n  memid.initially_committed = is_committed;\n  memid.initially_zero = is_zero;\n  memid.is_pinned = is_large;\n  return mi_manage_os_memory_ex2(start,size,is_large,numa_node,exclusive,memid, arena_id);\n}\n\n// Reserve a range of regular OS memory\nint mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {\n  if (arena_id != NULL) *arena_id = _mi_arena_id_none();\n  size = _mi_align_up(size, MI_ARENA_BLOCK_SIZE); // at least one block\n  mi_memid_t memid;\n  void* start = _mi_os_alloc_aligned(size, MI_SEGMENT_ALIGN, commit, allow_large, &memid);\n  if (start == NULL) return ENOMEM;\n  const bool is_large = memid.is_pinned; // todo: use separate is_large field?\n  if (!mi_manage_os_memory_ex2(start, size, is_large, -1 /* numa node */, exclusive, memid, arena_id)) {\n    _mi_os_free_ex(start, size, commit, memid);\n    _mi_verbose_message(\"failed to reserve %zu KiB memory\\n\", _mi_divide_up(size, 1024));\n    return ENOMEM;\n  }\n  _mi_verbose_message(\"reserved %zu KiB memory%s\\n\", _mi_divide_up(size, 1024), is_large ? \" (in large os pages)\" : \"\");\n  return 0;\n}\n\n\n// Manage a range of regular OS memory\nbool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept {\n  return mi_manage_os_memory_ex(start, size, is_committed, is_large, is_zero, numa_node, false /* exclusive? */, NULL);\n}\n\n// Reserve a range of regular OS memory\nint mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept {\n  return mi_reserve_os_memory_ex(size, commit, allow_large, false, NULL);\n}\n\n\n/* -----------------------------------------------------------\n  Debugging\n----------------------------------------------------------- */\n\nstatic size_t mi_debug_show_bitmap(const char* prefix, const char* header, size_t block_count, mi_bitmap_field_t* fields, size_t field_count ) {\n  _mi_message(\"%s%s:\\n\", prefix, header);\n  size_t bcount = 0;\n  size_t inuse_count = 0;\n  for (size_t i = 0; i < field_count; i++) {\n    char buf[MI_BITMAP_FIELD_BITS + 1];\n    uintptr_t field = mi_atomic_load_relaxed(&fields[i]);\n    for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++, bcount++) {\n      if (bcount < block_count) {\n        bool inuse = ((((uintptr_t)1 << bit) & field) != 0);\n        if (inuse) inuse_count++;\n        buf[bit] = (inuse ? 'x' : '.');\n      }\n      else {\n        buf[bit] = ' ';\n      }\n    }\n    buf[MI_BITMAP_FIELD_BITS] = 0;\n    _mi_message(\"%s  %s\\n\", prefix, buf);\n  }\n  _mi_message(\"%s  total ('x'): %zu\\n\", prefix, inuse_count);\n  return inuse_count;\n}\n\nvoid mi_debug_show_arenas(void) mi_attr_noexcept {\n  const bool show_inuse = true;\n  size_t max_arenas = mi_atomic_load_relaxed(&mi_arena_count);\n  size_t inuse_total = 0;\n  //size_t abandoned_total = 0;\n  //size_t purge_total = 0;\n  for (size_t i = 0; i < max_arenas; i++) {\n    mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);\n    if (arena == NULL) break;\n    _mi_message(\"arena %zu: %zu blocks of size %zuMiB (in %zu fields) %s\\n\", i, arena->block_count, (size_t)(MI_ARENA_BLOCK_SIZE / MI_MiB), arena->field_count, (arena->memid.is_pinned ? \", pinned\" : \"\"));\n    if (show_inuse) {\n      inuse_total += mi_debug_show_bitmap(\"  \", \"inuse blocks\", arena->block_count, arena->blocks_inuse, arena->field_count);\n    }\n    if (arena->blocks_committed != NULL) {\n      mi_debug_show_bitmap(\"  \", \"committed blocks\", arena->block_count, arena->blocks_committed, arena->field_count);\n    }\n    //if (show_abandoned) {\n    //  abandoned_total += mi_debug_show_bitmap(\"  \", \"abandoned blocks\", arena->block_count, arena->blocks_abandoned, arena->field_count);\n    //}\n    //if (show_purge && arena->blocks_purge != NULL) {\n    //  purge_total += mi_debug_show_bitmap(\"  \", \"purgeable blocks\", arena->block_count, arena->blocks_purge, arena->field_count);\n    //}\n  }\n  if (show_inuse)     _mi_message(\"total inuse blocks    : %zu\\n\", inuse_total);\n  //if (show_abandoned) _mi_message(\"total abandoned blocks: %zu\\n\", abandoned_total);\n  //if (show_purge)     _mi_message(\"total purgeable blocks: %zu\\n\", purge_total);\n}\n\n\nvoid mi_arenas_print(void) mi_attr_noexcept {\n  mi_debug_show_arenas();\n}\n\n\n/* -----------------------------------------------------------\n  Reserve a huge page arena.\n----------------------------------------------------------- */\n// reserve at a specific numa node\nint mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {\n  if (arena_id != NULL) *arena_id = -1;\n  if (pages==0) return 0;\n  if (numa_node < -1) numa_node = -1;\n  if (numa_node >= 0) numa_node = numa_node % _mi_os_numa_node_count();\n  size_t hsize = 0;\n  size_t pages_reserved = 0;\n  mi_memid_t memid;\n  void* p = _mi_os_alloc_huge_os_pages(pages, numa_node, timeout_msecs, &pages_reserved, &hsize, &memid);\n  if (p==NULL || pages_reserved==0) {\n    _mi_warning_message(\"failed to reserve %zu GiB huge pages\\n\", pages);\n    return ENOMEM;\n  }\n  _mi_verbose_message(\"numa node %i: reserved %zu GiB huge pages (of the %zu GiB requested)\\n\", numa_node, pages_reserved, pages);\n\n  if (!mi_manage_os_memory_ex2(p, hsize, true, numa_node, exclusive, memid, arena_id)) {\n    _mi_os_free(p, hsize, memid);\n    return ENOMEM;\n  }\n  return 0;\n}\n\nint mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept {\n  return mi_reserve_huge_os_pages_at_ex(pages, numa_node, timeout_msecs, false, NULL);\n}\n\n// reserve huge pages evenly among the given number of numa nodes (or use the available ones as detected)\nint mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept {\n  if (pages == 0) return 0;\n\n  // pages per numa node\n  int numa_count = (numa_nodes > 0 && numa_nodes <= INT_MAX ? (int)numa_nodes : _mi_os_numa_node_count());\n  if (numa_count == 0) numa_count = 1;\n  const size_t pages_per = pages / numa_count;\n  const size_t pages_mod = pages % numa_count;\n  const size_t timeout_per = (timeout_msecs==0 ? 0 : (timeout_msecs / numa_count) + 50);\n\n  // reserve evenly among numa nodes\n  for (int numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) {\n    size_t node_pages = pages_per;  // can be 0\n    if ((size_t)numa_node < pages_mod) node_pages++;\n    int err = mi_reserve_huge_os_pages_at(node_pages, numa_node, timeout_per);\n    if (err) return err;\n    if (pages < node_pages) {\n      pages = 0;\n    }\n    else {\n      pages -= node_pages;\n    }\n  }\n\n  return 0;\n}\n\nint mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept {\n  MI_UNUSED(max_secs);\n  _mi_warning_message(\"mi_reserve_huge_os_pages is deprecated: use mi_reserve_huge_os_pages_interleave/at instead\\n\");\n  if (pages_reserved != NULL) *pages_reserved = 0;\n  int err = mi_reserve_huge_os_pages_interleave(pages, 0, (size_t)(max_secs * 1000.0));\n  if (err==0 && pages_reserved!=NULL) *pages_reserved = pages;\n  return err;\n}\n"
  },
  {
    "path": "src/bitmap.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2023 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* ----------------------------------------------------------------------------\nConcurrent bitmap that can set/reset sequences of bits atomically,\nrepresented as an array of fields where each field is a machine word (`size_t`)\n\nThere are two api's; the standard one cannot have sequences that cross\nbetween the bitmap fields (and a sequence must be <= MI_BITMAP_FIELD_BITS).\n\nThe `_across` postfixed functions do allow sequences that can cross over\nbetween the fields. (This is used in arena allocation)\n---------------------------------------------------------------------------- */\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"bitmap.h\"\n\n/* -----------------------------------------------------------\n  Bitmap definition\n----------------------------------------------------------- */\n\n// The bit mask for a given number of blocks at a specified bit index.\nstatic inline size_t mi_bitmap_mask_(size_t count, size_t bitidx) {\n  mi_assert_internal(count + bitidx <= MI_BITMAP_FIELD_BITS);\n  mi_assert_internal(count > 0);\n  if (count >= MI_BITMAP_FIELD_BITS) return MI_BITMAP_FIELD_FULL;\n  if (count == 0) return 0;\n  return ((((size_t)1 << count) - 1) << bitidx);\n}\n\n\n/* -----------------------------------------------------------\n  Claim a bit sequence atomically\n----------------------------------------------------------- */\n\n// Try to atomically claim a sequence of `count` bits in a single\n// field at `idx` in `bitmap`. Returns `true` on success.\ninline bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx)\n{\n  mi_assert_internal(bitmap_idx != NULL);\n  mi_assert_internal(count <= MI_BITMAP_FIELD_BITS);\n  mi_assert_internal(count > 0);\n  mi_bitmap_field_t* field = &bitmap[idx];\n  size_t map  = mi_atomic_load_relaxed(field);\n  if (map==MI_BITMAP_FIELD_FULL) return false; // short cut\n\n  // search for 0-bit sequence of length count\n  const size_t mask = mi_bitmap_mask_(count, 0);\n  const size_t bitidx_max = MI_BITMAP_FIELD_BITS - count;\n\n#ifdef MI_HAVE_FAST_BITSCAN\n  size_t bitidx = mi_ctz(~map);    // quickly find the first zero bit if possible\n#else\n  size_t bitidx = 0;               // otherwise start at 0\n#endif\n  size_t m = (mask << bitidx);     // invariant: m == mask shifted by bitidx\n\n  // scan linearly for a free range of zero bits\n  while (bitidx <= bitidx_max) {\n    const size_t mapm = (map & m);\n    if (mapm == 0) {  // are the mask bits free at bitidx?\n      mi_assert_internal((m >> bitidx) == mask); // no overflow?\n      const size_t newmap = (map | m);\n      mi_assert_internal((newmap^map) >> bitidx == mask);\n      if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) {  // TODO: use weak cas here?\n        // no success, another thread claimed concurrently.. keep going (with updated `map`)\n        continue;\n      }\n      else {\n        // success, we claimed the bits!\n        *bitmap_idx = mi_bitmap_index_create(idx, bitidx);\n        return true;\n      }\n    }\n    else {\n      // on to the next bit range\n#ifdef MI_HAVE_FAST_BITSCAN\n      mi_assert_internal(mapm != 0);\n      const size_t shift = (count == 1 ? 1 : (MI_SIZE_BITS - mi_clz(mapm) - bitidx));\n      mi_assert_internal(shift > 0 && shift <= count);\n#else\n      const size_t shift = 1;\n#endif\n      bitidx += shift;\n      m <<= shift;\n    }\n  }\n  // no bits found\n  return false;\n}\n\n// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success.\n// Starts at idx, and wraps around to search in all `bitmap_fields` fields.\n// `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.\nbool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) {\n  size_t idx = start_field_idx;\n  for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {\n    if (idx >= bitmap_fields) { idx = 0; } // wrap\n    if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled\nbool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields, \n            const size_t start_field_idx, const size_t count, \n            mi_bitmap_pred_fun_t pred_fun, void* pred_arg,            \n            mi_bitmap_index_t* bitmap_idx) {\n  size_t idx = start_field_idx;\n  for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {\n    if (idx >= bitmap_fields) idx = 0; // wrap\n    if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {\n      if (pred_fun == NULL || pred_fun(*bitmap_idx, pred_arg)) { \n        return true;\n      }\n      // predicate returned false, unclaim and look further\n      _mi_bitmap_unclaim(bitmap, bitmap_fields, count, *bitmap_idx);\n    }\n  }\n  return false;\n}\n\n// Set `count` bits at `bitmap_idx` to 0 atomically\n// Returns `true` if all `count` bits were 1 previously.\nbool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  const size_t idx = mi_bitmap_index_field(bitmap_idx);\n  const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);\n  const size_t mask = mi_bitmap_mask_(count, bitidx);\n  mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);\n  // mi_assert_internal((bitmap[idx] & mask) == mask);\n  const size_t prev = mi_atomic_and_acq_rel(&bitmap[idx], ~mask);\n  return ((prev & mask) == mask);\n}\n\n\n// Set `count` bits at `bitmap_idx` to 1 atomically\n// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.\nbool _mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero) {\n  const size_t idx = mi_bitmap_index_field(bitmap_idx);\n  const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);\n  const size_t mask = mi_bitmap_mask_(count, bitidx);\n  mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);\n  //mi_assert_internal(any_zero != NULL || (bitmap[idx] & mask) == 0);\n  size_t prev = mi_atomic_or_acq_rel(&bitmap[idx], mask);\n  if (any_zero != NULL) { *any_zero = ((prev & mask) != mask); }\n  return ((prev & mask) == 0);\n}\n\n// Returns `true` if all `count` bits were 1. `any_ones` is `true` if there was at least one bit set to one.\nstatic bool mi_bitmap_is_claimedx(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_ones) {\n  const size_t idx = mi_bitmap_index_field(bitmap_idx);\n  const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);\n  const size_t mask = mi_bitmap_mask_(count, bitidx);\n  mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);\n  const size_t field = mi_atomic_load_relaxed(&bitmap[idx]);\n  if (any_ones != NULL) { *any_ones = ((field & mask) != 0); }\n  return ((field & mask) == mask);\n}\n\n// Try to set `count` bits at `bitmap_idx` from 0 to 1 atomically.\n// Returns `true` if successful when all previous `count` bits were 0.\nbool _mi_bitmap_try_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  const size_t idx = mi_bitmap_index_field(bitmap_idx);\n  const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);\n  const size_t mask = mi_bitmap_mask_(count, bitidx);\n  mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields);\n  size_t expected = mi_atomic_load_relaxed(&bitmap[idx]);\n  do  {\n    if ((expected & mask) != 0) return false;\n  }\n  while (!mi_atomic_cas_strong_acq_rel(&bitmap[idx], &expected, expected | mask));\n  mi_assert_internal((expected & mask) == 0);\n  return true;\n}\n\n\nbool _mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  return mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, NULL);\n}\n\nbool _mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  bool any_ones;\n  mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, &any_ones);\n  return any_ones;\n}\n\n\n//--------------------------------------------------------------------------\n// the `_across` functions work on bitmaps where sequences can cross over\n// between the fields. This is used in arena allocation\n//--------------------------------------------------------------------------\n\n// Try to atomically claim a sequence of `count` bits starting from the field\n// at `idx` in `bitmap` and crossing into subsequent fields. Returns `true` on success.\n// Only needs to consider crossing into the next fields (see `mi_bitmap_try_find_from_claim_across`)\nstatic bool mi_bitmap_try_find_claim_field_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t idx, const size_t count, const size_t retries, mi_bitmap_index_t* bitmap_idx)\n{\n  mi_assert_internal(bitmap_idx != NULL);\n\n  // check initial trailing zeros\n  mi_bitmap_field_t* field = &bitmap[idx];\n  size_t map = mi_atomic_load_relaxed(field);\n  const size_t initial = mi_clz(map);  // count of initial zeros starting at idx\n  mi_assert_internal(initial <= MI_BITMAP_FIELD_BITS);\n  if (initial == 0)     return false;\n  if (initial >= count) return _mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx);    // no need to cross fields (this case won't happen for us)\n  if (_mi_divide_up(count - initial, MI_BITMAP_FIELD_BITS) >= (bitmap_fields - idx)) return false; // not enough entries\n\n  // scan ahead\n  size_t found = initial;\n  size_t mask = 0;     // mask bits for the final field\n  while(found < count) {\n    field++;\n    map = mi_atomic_load_relaxed(field);\n    const size_t mask_bits = (found + MI_BITMAP_FIELD_BITS <= count ? MI_BITMAP_FIELD_BITS : (count - found));\n    mi_assert_internal(mask_bits > 0 && mask_bits <= MI_BITMAP_FIELD_BITS);\n    mask = mi_bitmap_mask_(mask_bits, 0);\n    if ((map & mask) != 0) return false;  // some part is already claimed\n    found += mask_bits;\n  }\n  mi_assert_internal(field < &bitmap[bitmap_fields]);\n\n  // we found a range of contiguous zeros up to the final field; mask contains mask in the final field\n  // now try to claim the range atomically\n  mi_bitmap_field_t* const final_field = field;\n  const size_t final_mask = mask;\n  mi_bitmap_field_t* const initial_field = &bitmap[idx];\n  const size_t initial_idx = MI_BITMAP_FIELD_BITS - initial;\n  const size_t initial_mask = mi_bitmap_mask_(initial, initial_idx);\n\n  // initial field\n  size_t newmap;\n  field = initial_field;\n  map = mi_atomic_load_relaxed(field);\n  do {\n    newmap = (map | initial_mask);\n    if ((map & initial_mask) != 0) { goto rollback; };\n  } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));\n\n  // intermediate fields\n  while (++field < final_field) {\n    newmap = MI_BITMAP_FIELD_FULL;\n    map = 0;\n    if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { goto rollback; }\n  }\n\n  // final field\n  mi_assert_internal(field == final_field);\n  map = mi_atomic_load_relaxed(field);\n  do {\n    newmap = (map | final_mask);\n    if ((map & final_mask) != 0) { goto rollback; }\n  } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));\n\n  // claimed!\n  *bitmap_idx = mi_bitmap_index_create(idx, initial_idx);\n  return true;\n\nrollback:\n  // roll back intermediate fields\n  // (we just failed to claim `field` so decrement first)\n  while (--field > initial_field) {\n    newmap = 0;\n    map = MI_BITMAP_FIELD_FULL;\n    mi_assert_internal(mi_atomic_load_relaxed(field) == map);\n    mi_atomic_store_release(field, newmap);\n  }\n  if (field == initial_field) {               // (if we failed on the initial field, `field + 1 == initial_field`)\n    map = mi_atomic_load_relaxed(field);\n    do {\n      mi_assert_internal((map & initial_mask) == initial_mask);\n      newmap = (map & ~initial_mask);\n    } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));\n  }\n  mi_stat_counter_increase(_mi_stats_main.arena_rollback_count,1);\n  // retry? (we make a recursive call instead of goto to be able to use const declarations)\n  if (retries <= 2) {\n    return mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, retries+1, bitmap_idx);\n  }\n  else {\n    return false;\n  }\n}\n\n\n// Find `count` bits of zeros and set them to 1 atomically; returns `true` on success.\n// Starts at idx, and wraps around to search in all `bitmap_fields` fields.\nbool _mi_bitmap_try_find_from_claim_across(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) {\n  mi_assert_internal(count > 0);\n  if (count <= 2) {\n    // we don't bother with crossover fields for small counts\n    return _mi_bitmap_try_find_from_claim(bitmap, bitmap_fields, start_field_idx, count, bitmap_idx);\n  }\n\n  // visit the fields\n  size_t idx = start_field_idx;\n  for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) {\n    if (idx >= bitmap_fields) { idx = 0; } // wrap\n    // first try to claim inside a field\n    /*\n    if (count <= MI_BITMAP_FIELD_BITS) {\n      if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) {\n        return true;\n      }\n    }\n    */\n    // if that fails, then try to claim across fields\n    if (mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, 0, bitmap_idx)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// Helper for masks across fields; returns the mid count, post_mask may be 0\nstatic size_t mi_bitmap_mask_across(mi_bitmap_index_t bitmap_idx, size_t bitmap_fields, size_t count, size_t* pre_mask, size_t* mid_mask, size_t* post_mask) {\n  MI_UNUSED(bitmap_fields);\n  const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx);\n  if mi_likely(bitidx + count <= MI_BITMAP_FIELD_BITS) {\n    *pre_mask = mi_bitmap_mask_(count, bitidx);\n    *mid_mask = 0;\n    *post_mask = 0;\n    mi_assert_internal(mi_bitmap_index_field(bitmap_idx) < bitmap_fields);\n    return 0;\n  }\n  else {\n    const size_t pre_bits = MI_BITMAP_FIELD_BITS - bitidx;\n    mi_assert_internal(pre_bits < count);\n    *pre_mask = mi_bitmap_mask_(pre_bits, bitidx);\n    count -= pre_bits;\n    const size_t mid_count = (count / MI_BITMAP_FIELD_BITS);\n    *mid_mask = MI_BITMAP_FIELD_FULL;\n    count %= MI_BITMAP_FIELD_BITS;\n    *post_mask = (count==0 ? 0 : mi_bitmap_mask_(count, 0));\n    mi_assert_internal(mi_bitmap_index_field(bitmap_idx) + mid_count + (count==0 ? 0 : 1) < bitmap_fields);\n    return mid_count;\n  }\n}\n\n// Set `count` bits at `bitmap_idx` to 0 atomically\n// Returns `true` if all `count` bits were 1 previously.\nbool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  size_t idx = mi_bitmap_index_field(bitmap_idx);\n  size_t pre_mask;\n  size_t mid_mask;\n  size_t post_mask;\n  size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);\n  bool all_one = true;\n  mi_bitmap_field_t* field = &bitmap[idx];\n  size_t prev = mi_atomic_and_acq_rel(field++, ~pre_mask);   // clear first part\n  if ((prev & pre_mask) != pre_mask) all_one = false;\n  while(mid_count-- > 0) {\n    prev = mi_atomic_and_acq_rel(field++, ~mid_mask);        // clear mid part\n    if ((prev & mid_mask) != mid_mask) all_one = false;\n  }\n  if (post_mask!=0) {\n    prev = mi_atomic_and_acq_rel(field, ~post_mask);         // clear end part\n    if ((prev & post_mask) != post_mask) all_one = false;\n  }\n  return all_one;\n}\n\n// Set `count` bits at `bitmap_idx` to 1 atomically\n// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.\nbool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero, size_t* already_set) {\n  size_t idx = mi_bitmap_index_field(bitmap_idx);\n  size_t pre_mask;\n  size_t mid_mask;\n  size_t post_mask;\n  size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);\n  bool all_zero = true;\n  bool any_zero = false;\n  size_t one_count = 0;\n  _Atomic(size_t)*field = &bitmap[idx];\n  size_t prev = mi_atomic_or_acq_rel(field++, pre_mask);\n  if ((prev & pre_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & pre_mask); }\n  if ((prev & pre_mask) != pre_mask) any_zero = true;\n  while (mid_count-- > 0) {\n    prev = mi_atomic_or_acq_rel(field++, mid_mask);\n    if ((prev & mid_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & mid_mask); }\n    if ((prev & mid_mask) != mid_mask) any_zero = true;\n  }\n  if (post_mask!=0) {\n    prev = mi_atomic_or_acq_rel(field, post_mask);\n    if ((prev & post_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & post_mask); }\n    if ((prev & post_mask) != post_mask) any_zero = true;\n  }\n  if (pany_zero != NULL) { *pany_zero = any_zero; }\n  if (already_set != NULL) { *already_set = one_count; };\n  mi_assert_internal(all_zero ? one_count == 0 : one_count <= count);\n  return all_zero;\n}\n\n\n// Returns `true` if all `count` bits were 1.\n// `any_ones` is `true` if there was at least one bit set to one.\nstatic bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_ones, size_t* already_set) {\n  size_t idx = mi_bitmap_index_field(bitmap_idx);\n  size_t pre_mask;\n  size_t mid_mask;\n  size_t post_mask;\n  size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);\n  bool all_ones = true;\n  bool any_ones = false;\n  size_t one_count = 0;\n  mi_bitmap_field_t* field = &bitmap[idx];\n  size_t prev = mi_atomic_load_relaxed(field++);\n  if ((prev & pre_mask) != pre_mask) all_ones = false;\n  if ((prev & pre_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & pre_mask); }\n  while (mid_count-- > 0) {\n    prev = mi_atomic_load_relaxed(field++);\n    if ((prev & mid_mask) != mid_mask) all_ones = false;\n    if ((prev & mid_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & mid_mask); }\n  }\n  if (post_mask!=0) {\n    prev = mi_atomic_load_relaxed(field);\n    if ((prev & post_mask) != post_mask) all_ones = false;\n    if ((prev & post_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & post_mask); }\n  }\n  if (pany_ones != NULL) { *pany_ones = any_ones; }\n  if (already_set != NULL) { *already_set = one_count; }\n  mi_assert_internal(all_ones ? one_count == count : one_count < count);\n  return all_ones;\n}\n\nbool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, size_t* already_set) {\n  return mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, NULL, already_set);\n}\n\nbool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {\n  bool any_ones;\n  mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, &any_ones, NULL);\n  return any_ones;\n}\n"
  },
  {
    "path": "src/bitmap.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2023 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* ----------------------------------------------------------------------------\nConcurrent bitmap that can set/reset sequences of bits atomically,\nrepresented as an array of fields where each field is a machine word (`size_t`)\n\nThere are two api's; the standard one cannot have sequences that cross\nbetween the bitmap fields (and a sequence must be <= MI_BITMAP_FIELD_BITS).\n(this is used in region allocation)\n\nThe `_across` postfixed functions do allow sequences that can cross over\nbetween the fields. (This is used in arena allocation)\n---------------------------------------------------------------------------- */\n#pragma once\n#ifndef MI_BITMAP_H\n#define MI_BITMAP_H\n\n/* -----------------------------------------------------------\n  Bitmap definition\n----------------------------------------------------------- */\n\n#define MI_BITMAP_FIELD_BITS   (8*MI_SIZE_SIZE)\n#define MI_BITMAP_FIELD_FULL   (~((size_t)0))   // all bits set\n\n// An atomic bitmap of `size_t` fields\ntypedef _Atomic(size_t)  mi_bitmap_field_t;\ntypedef mi_bitmap_field_t*  mi_bitmap_t;\n\n// A bitmap index is the index of the bit in a bitmap.\ntypedef size_t mi_bitmap_index_t;\n\n// Create a bit index.\nstatic inline mi_bitmap_index_t mi_bitmap_index_create_ex(size_t idx, size_t bitidx) {\n  mi_assert_internal(bitidx <= MI_BITMAP_FIELD_BITS);\n  return (idx*MI_BITMAP_FIELD_BITS) + bitidx;\n}\nstatic inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx) {\n  mi_assert_internal(bitidx < MI_BITMAP_FIELD_BITS);\n  return mi_bitmap_index_create_ex(idx,bitidx);\n}\n\n// Create a bit index.\nstatic inline mi_bitmap_index_t mi_bitmap_index_create_from_bit(size_t full_bitidx) {  \n  return mi_bitmap_index_create(full_bitidx / MI_BITMAP_FIELD_BITS, full_bitidx % MI_BITMAP_FIELD_BITS);\n}\n\n// Get the field index from a bit index.\nstatic inline size_t mi_bitmap_index_field(mi_bitmap_index_t bitmap_idx) {\n  return (bitmap_idx / MI_BITMAP_FIELD_BITS);\n}\n\n// Get the bit index in a bitmap field\nstatic inline size_t mi_bitmap_index_bit_in_field(mi_bitmap_index_t bitmap_idx) {\n  return (bitmap_idx % MI_BITMAP_FIELD_BITS);\n}\n\n// Get the full bit index\nstatic inline size_t mi_bitmap_index_bit(mi_bitmap_index_t bitmap_idx) {\n  return bitmap_idx;\n}\n\n/* -----------------------------------------------------------\n  Claim a bit sequence atomically\n----------------------------------------------------------- */\n\n// Try to atomically claim a sequence of `count` bits in a single\n// field at `idx` in `bitmap`. Returns `true` on success.\nbool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx);\n\n// Starts at idx, and wraps around to search in all `bitmap_fields` fields.\n// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.\nbool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx);\n\n// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled\ntypedef bool (mi_cdecl *mi_bitmap_pred_fun_t)(mi_bitmap_index_t bitmap_idx, void* pred_arg);\nbool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_pred_fun_t pred_fun, void* pred_arg, mi_bitmap_index_t* bitmap_idx);\n\n// Set `count` bits at `bitmap_idx` to 0 atomically\n// Returns `true` if all `count` bits were 1 previously.\nbool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\n\n// Try to set `count` bits at `bitmap_idx` from 0 to 1 atomically. \n// Returns `true` if successful when all previous `count` bits were 0.\nbool _mi_bitmap_try_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\n\n// Set `count` bits at `bitmap_idx` to 1 atomically\n// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.\nbool _mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero);\n\nbool _mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\nbool _mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\n\n\n//--------------------------------------------------------------------------\n// the `_across` functions work on bitmaps where sequences can cross over\n// between the fields. This is used in arena allocation\n//--------------------------------------------------------------------------\n\n// Find `count` bits of zeros and set them to 1 atomically; returns `true` on success.\n// Starts at idx, and wraps around to search in all `bitmap_fields` fields.\nbool _mi_bitmap_try_find_from_claim_across(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx);\n\n// Set `count` bits at `bitmap_idx` to 0 atomically\n// Returns `true` if all `count` bits were 1 previously.\nbool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\n\n// Set `count` bits at `bitmap_idx` to 1 atomically\n// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.\nbool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero, size_t* already_set);\n\nbool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, size_t* already_set);\nbool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);\n\n#endif\n"
  },
  {
    "path": "src/free.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#if !defined(MI_IN_ALLOC_C)\n#error \"this file should be included from 'alloc.c' (so aliases can work from alloc-override)\"\n// add includes help an IDE\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"   // _mi_prim_thread_id()\n#endif\n\n// forward declarations\nstatic void   mi_check_padding(const mi_page_t* page, const mi_block_t* block);\nstatic bool   mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block);\nstatic size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block);\nstatic void   mi_stat_free(const mi_page_t* page, const mi_block_t* block);\n\n\n// ------------------------------------------------------\n// Free\n// ------------------------------------------------------\n\n// forward declaration of multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)\nstatic mi_decl_noinline void mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block);\n\n// regular free of a (thread local) block pointer\n// fast path written carefully to prevent spilling on the stack\nstatic inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool track_stats, bool check_full)\n{\n  // checks\n  if mi_unlikely(mi_check_is_double_free(page, block)) return;\n  mi_check_padding(page, block);\n  if (track_stats) { mi_stat_free(page, block); }\n  #if (MI_DEBUG>0) && !MI_TRACK_ENABLED  && !MI_TSAN && !MI_GUARDED\n  if (!mi_page_is_huge(page)) {   // huge page content may be already decommitted\n    memset(block, MI_DEBUG_FREED, mi_page_block_size(page));\n  }\n  #endif\n  if (track_stats) { mi_track_free_size(block, mi_page_usable_size_of(page, block)); } // faster then mi_usable_size as we already know the page and that p is unaligned\n\n  // actual free: push on the local free list\n  mi_block_set_next(page, block, page->local_free);\n  page->local_free = block;\n  if mi_unlikely(--page->used == 0) {\n    _mi_page_retire(page);\n  }\n  else if mi_unlikely(check_full && mi_page_is_in_full(page)) {\n    _mi_page_unfull(page);\n  }\n}\n\n// Adjust a block that was allocated aligned, to the actual start of the block in the page.\n// note: this can be called from `mi_free_generic_mt` where a non-owning thread accesses the\n// `page_start` and `block_size` fields; however these are constant and the page won't be\n// deallocated (as the block we are freeing keeps it alive) and thus safe to read concurrently.\nmi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p) {\n  mi_assert_internal(page!=NULL && p!=NULL);\n\n  size_t diff = (uint8_t*)p - page->page_start;\n  size_t adjust;\n  if mi_likely(page->block_size_shift != 0) {\n    adjust = diff & (((size_t)1 << page->block_size_shift) - 1);\n  }\n  else {\n    adjust = diff % mi_page_block_size(page);\n  }\n\n  return (mi_block_t*)((uintptr_t)p - adjust);\n}\n\n// forward declaration for a MI_GUARDED build\n#if MI_GUARDED\nstatic void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p); // forward declaration\nstatic inline void mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {\n  if (mi_block_ptr_is_guarded(block, p)) { mi_block_unguard(page, block, p); }\n}\n#else\nstatic inline void mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {\n  MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(p);\n}\n#endif\n\n// free a local pointer  (page parameter comes first for better codegen)\nstatic void mi_decl_noinline mi_free_generic_local(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept {\n  MI_UNUSED(segment);\n  mi_block_t* const block = (mi_page_has_aligned(page) ? _mi_page_ptr_unalign(page, p) : (mi_block_t*)p);\n  mi_block_check_unguard(page, block, p);\n  mi_free_block_local(page, block, true /* track stats */, true /* check for a full page */);\n}\n\n// free a pointer owned by another thread (page parameter comes first for better codegen)\nstatic void mi_decl_noinline mi_free_generic_mt(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept {\n  mi_block_t* const block = _mi_page_ptr_unalign(page, p); // don't check `has_aligned` flag to avoid a race (issue #865)\n  mi_block_check_unguard(page, block, p);\n  mi_free_block_mt(page, segment, block);\n}\n\n// generic free (for runtime integration)\nvoid mi_decl_noinline _mi_free_generic(mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept {\n  if (is_local) mi_free_generic_local(page,segment,p);\n           else mi_free_generic_mt(page,segment,p);\n}\n\n// Get the segment data belonging to a pointer\n// This is just a single `and` in release mode but does further checks in debug mode\n// (and secure mode) to see if this was a valid pointer.\nstatic inline mi_segment_t* mi_checked_ptr_segment(const void* p, const char* msg)\n{\n  MI_UNUSED(msg);\n\n  #if (MI_DEBUG>0)\n  if mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0 && !mi_option_is_enabled(mi_option_guarded_precise)) {\n    _mi_error_message(EINVAL, \"%s: invalid (unaligned) pointer: %p\\n\", msg, p);\n    return NULL;\n  }\n  #endif\n\n  mi_segment_t* const segment = _mi_ptr_segment(p);\n  if mi_unlikely(segment==NULL) return segment;\n\n  #if (MI_DEBUG>0)\n  if mi_unlikely(!mi_is_in_heap_region(p)) {\n  #if (MI_INTPTR_SIZE == 8 && defined(__linux__))\n    if (((uintptr_t)p >> 40) != 0x7F) { // linux tends to align large blocks above 0x7F000000000 (issue #640)\n  #else\n    {\n  #endif\n      _mi_warning_message(\"%s: pointer might not point to a valid heap region: %p\\n\"\n        \"(this may still be a valid very large allocation (over 64MiB))\\n\", msg, p);\n      if mi_likely(_mi_ptr_cookie(segment) == segment->cookie) {\n        _mi_warning_message(\"(yes, the previous pointer %p was valid after all)\\n\", p);\n      }\n    }\n  }\n  #endif\n  #if (MI_DEBUG>0 || MI_SECURE>=4)\n  if mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie) {\n    _mi_error_message(EINVAL, \"%s: pointer does not point to a valid heap space: %p\\n\", msg, p);\n    return NULL;\n  }\n  #endif\n\n  return segment;\n}\n\n// Free a block\n// Fast path written carefully to prevent register spilling on the stack\nstatic inline void mi_free_ex(void* p, size_t* usable) mi_attr_noexcept\n{\n  mi_segment_t* const segment = mi_checked_ptr_segment(p,\"mi_free\");\n  if mi_unlikely(segment==NULL) return;\n\n  const bool is_local = (_mi_prim_thread_id() == mi_atomic_load_relaxed(&segment->thread_id));\n  mi_page_t* const page = _mi_segment_page_of(segment, p);\n  if (usable!=NULL) { *usable = mi_page_usable_block_size(page); }\n  \n  if mi_likely(is_local) {                        // thread-local free?\n    if mi_likely(page->flags.full_aligned == 0) { // and it is not a full page (full pages need to move from the full bin), nor has aligned blocks (aligned blocks need to be unaligned)\n      // thread-local, aligned, and not a full page\n      mi_block_t* const block = (mi_block_t*)p;\n      mi_free_block_local(page, block, true /* track stats */, false /* no need to check if the page is full */);\n    }\n    else {\n      // page is full or contains (inner) aligned blocks; use generic path\n      mi_free_generic_local(page, segment, p);\n    }\n  }\n  else {\n    // not thread-local; use generic path\n    mi_free_generic_mt(page, segment, p);\n  }\n}\n\nvoid mi_free(void* p) mi_attr_noexcept {\n  mi_free_ex(p,NULL);\n}\n\nvoid mi_ufree(void* p, size_t* usable) mi_attr_noexcept {\n  mi_free_ex(p,usable);\n}\n\n// return true if successful\nbool _mi_free_delayed_block(mi_block_t* block) {\n  // get segment and page\n  mi_assert_internal(block!=NULL);\n  const mi_segment_t* const segment = _mi_ptr_segment(block);\n  mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie);\n  mi_assert_internal(_mi_thread_id() == segment->thread_id);\n  mi_page_t* const page = _mi_segment_page_of(segment, block);\n\n  // Clear the no-delayed flag so delayed freeing is used again for this page.\n  // This must be done before collecting the free lists on this page -- otherwise\n  // some blocks may end up in the page `thread_free` list with no blocks in the\n  // heap `thread_delayed_free` list which may cause the page to be never freed!\n  // (it would only be freed if we happen to scan it in `mi_page_queue_find_free_ex`)\n  if (!_mi_page_try_use_delayed_free(page, MI_USE_DELAYED_FREE, false /* dont overwrite never delayed */)) {\n    return false;\n  }\n\n  // collect all other non-local frees (move from `thread_free` to `free`) to ensure up-to-date `used` count\n  _mi_page_free_collect(page, false);\n\n  // and free the block (possibly freeing the page as well since `used` is updated)\n  mi_free_block_local(page, block, false /* stats have already been adjusted */, true /* check for a full page */);\n  return true;\n}\n\n// ------------------------------------------------------\n// Multi-threaded Free (`_mt`)\n// ------------------------------------------------------\n\n// Push a block that is owned by another thread on its page-local thread free\n// list or it's heap delayed free list. Such blocks are later collected by\n// the owning thread in `_mi_free_delayed_block`.\nstatic void mi_decl_noinline mi_free_block_delayed_mt( mi_page_t* page, mi_block_t* block )\n{\n  // Try to put the block on either the page-local thread free list,\n  // or the heap delayed free list (if this is the first non-local free in that page)\n  mi_thread_free_t tfreex;\n  bool use_delayed;\n  mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free);\n  do {\n    use_delayed = (mi_tf_delayed(tfree) == MI_USE_DELAYED_FREE);\n    if mi_unlikely(use_delayed) {\n      // unlikely: this only happens on the first concurrent free in a page that is in the full list\n      tfreex = mi_tf_set_delayed(tfree,MI_DELAYED_FREEING);\n    }\n    else {\n      // usual: directly add to page thread_free list\n      mi_block_set_next(page, block, mi_tf_block(tfree));\n      tfreex = mi_tf_set_block(tfree,block);\n    }\n  } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));\n\n  // If this was the first non-local free, we need to push it on the heap delayed free list instead\n  if mi_unlikely(use_delayed) {\n    // racy read on `heap`, but ok because MI_DELAYED_FREEING is set (see `mi_heap_delete` and `mi_heap_collect_abandon`)\n    mi_heap_t* const heap = (mi_heap_t*)(mi_atomic_load_acquire(&page->xheap)); //mi_page_heap(page);\n    mi_assert_internal(heap != NULL);\n    if (heap != NULL) {\n      // add to the delayed free list of this heap. (do this atomically as the lock only protects heap memory validity)\n      mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);\n      do {\n        mi_block_set_nextx(heap,block,dfree, heap->keys);\n      } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block));\n    }\n\n    // and reset the MI_DELAYED_FREEING flag\n    tfree = mi_atomic_load_relaxed(&page->xthread_free);\n    do {\n      tfreex = tfree;\n      mi_assert_internal(mi_tf_delayed(tfree) == MI_DELAYED_FREEING);\n      tfreex = mi_tf_set_delayed(tfree,MI_NO_DELAYED_FREE);\n    } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));\n  }\n}\n\n// Multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)\nstatic void mi_decl_noinline mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block)\n{\n  // first see if the segment was abandoned and if we can reclaim it into our thread\n  if (_mi_option_get_fast(mi_option_abandoned_reclaim_on_free) != 0 &&\n      #if MI_HUGE_PAGE_ABANDON\n      segment->page_kind != MI_PAGE_HUGE &&\n      #endif\n      mi_atomic_load_relaxed(&segment->thread_id) == 0 &&  // segment is abandoned?\n      mi_prim_get_default_heap() != (mi_heap_t*)&_mi_heap_empty) // and we did not already exit this thread (without this check, a fresh heap will be initalized (issue #944))\n  {\n    // the segment is abandoned, try to reclaim it into our heap\n    if (_mi_segment_attempt_reclaim(mi_heap_get_default(), segment)) {\n      mi_assert_internal(_mi_thread_id() == mi_atomic_load_relaxed(&segment->thread_id));\n      mi_assert_internal(mi_heap_get_default()->tld->segments.subproc == segment->subproc);\n      mi_free(block);  // recursively free as now it will be a local free in our heap\n      return;\n    }\n  }\n\n  // The padding check may access the non-thread-owned page for the key values.\n  // that is safe as these are constant and the page won't be freed (as the block is not freed yet).\n  mi_check_padding(page, block);\n\n  // adjust stats (after padding check and potentially recursive `mi_free` above)\n  mi_stat_free(page, block);    // stat_free may access the padding\n  mi_track_free_size(block, mi_page_usable_size_of(page,block));\n\n  // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection\n  _mi_padding_shrink(page, block, sizeof(mi_block_t));\n\n  if (segment->kind == MI_SEGMENT_HUGE) {\n    #if MI_HUGE_PAGE_ABANDON\n    // huge page segments are always abandoned and can be freed immediately\n    _mi_segment_huge_page_free(segment, page, block);\n    return;\n    #else\n    // huge pages are special as they occupy the entire segment\n    // as these are large we reset the memory occupied by the page so it is available to other threads\n    // (as the owning thread needs to actually free the memory later).\n    _mi_segment_huge_page_reset(segment, page, block);\n    #endif\n  }\n  else {\n    #if (MI_DEBUG>0) && !MI_TRACK_ENABLED  && !MI_TSAN       // note: when tracking, cannot use mi_usable_size with multi-threading\n    memset(block, MI_DEBUG_FREED, mi_usable_size(block));\n    #endif\n  }\n\n  // and finally free the actual block by pushing it on the owning heap\n  // thread_delayed free list (or heap delayed free list)\n  mi_free_block_delayed_mt(page,block);\n}\n\n\n// ------------------------------------------------------\n// Usable size\n// ------------------------------------------------------\n\n// Bytes available in a block\nstatic size_t mi_decl_noinline mi_page_usable_aligned_size_of(const mi_page_t* page, const void* p) mi_attr_noexcept {\n  const mi_block_t* block = _mi_page_ptr_unalign(page, p);\n  const size_t size = mi_page_usable_size_of(page, block);\n  const ptrdiff_t adjust = (uint8_t*)p - (uint8_t*)block;\n  mi_assert_internal(adjust >= 0 && (size_t)adjust <= size);\n  const size_t aligned_size = (size - adjust);\n  #if MI_GUARDED\n  if (mi_block_ptr_is_guarded(block, p)) {\n    return aligned_size - _mi_os_page_size();\n  }\n  #endif\n  return aligned_size;\n}\n\nstatic inline mi_page_t* mi_validate_ptr_page(const void* p, const char* msg) {\n  const mi_segment_t* const segment = mi_checked_ptr_segment(p, msg);\n  if mi_unlikely(segment==NULL) return NULL;\n  mi_page_t* const page = _mi_segment_page_of(segment, p);\n  return page;\n}\n\nstatic inline size_t _mi_usable_size(const void* p, const mi_page_t* page) mi_attr_noexcept {\n  if mi_unlikely(page==NULL) return 0;\n  if mi_likely(!mi_page_has_aligned(page)) {\n    const mi_block_t* block = (const mi_block_t*)p;\n    return mi_page_usable_size_of(page, block);\n  }\n  else {\n    // split out to separate routine for improved code generation\n    return mi_page_usable_aligned_size_of(page, p);\n  }\n}\n\nmi_decl_nodiscard size_t mi_usable_size(const void* p) mi_attr_noexcept {\n  const mi_page_t* const page = mi_validate_ptr_page(p,\"mi_usable_size\");\n  return _mi_usable_size(p,page);\n}\n\n\n// ------------------------------------------------------\n// Free variants\n// ------------------------------------------------------\n\nvoid mi_free_size(void* p, size_t size) mi_attr_noexcept {\n  MI_UNUSED_RELEASE(size);\n  #if MI_DEBUG\n  const mi_page_t* const page = mi_validate_ptr_page(p,\"mi_free_size\");  \n  const size_t available = _mi_usable_size(p,page);\n  mi_assert(p == NULL || size <= available || available == 0 /* invalid pointer */ );\n  #endif\n  mi_free(p);\n}\n\nvoid mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept {\n  MI_UNUSED_RELEASE(alignment);\n  mi_assert(((uintptr_t)p % alignment) == 0);\n  mi_free_size(p,size);\n}\n\nvoid mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept {\n  MI_UNUSED_RELEASE(alignment);\n  mi_assert(((uintptr_t)p % alignment) == 0);\n  mi_free(p);\n}\n\n\n// ------------------------------------------------------\n// Check for double free in secure and debug mode\n// This is somewhat expensive so only enabled for secure mode 4\n// ------------------------------------------------------\n\n#if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0))\n// linear check if the free list contains a specific element\nstatic bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem) {\n  while (list != NULL) {\n    if (elem==list) return true;\n    list = mi_block_next(page, list);\n  }\n  return false;\n}\n\nstatic mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) {\n  // The decoded value is in the same page (or NULL).\n  // Walk the free lists to verify positively if it is already freed\n  if (mi_list_contains(page, page->free, block) ||\n      mi_list_contains(page, page->local_free, block) ||\n      mi_list_contains(page, mi_page_thread_free(page), block))\n  {\n    _mi_error_message(EAGAIN, \"double free detected of block %p with size %zu\\n\", block, mi_page_block_size(page));\n    return true;\n  }\n  return false;\n}\n\n#define mi_track_page(page,access)  { size_t psize; void* pstart = _mi_page_start(_mi_page_segment(page),page,&psize); mi_track_mem_##access( pstart, psize); }\n\nstatic inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {\n  bool is_double_free = false;\n  mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field\n  if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 &&  // quick check: aligned pointer?\n      (n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL?\n  {\n    // Suspicious: decoded value a in block is in the same page (or NULL) -- maybe a double free?\n    // (continue in separate function to improve code generation)\n    is_double_free = mi_check_is_double_freex(page, block);\n  }\n  return is_double_free;\n}\n#else\nstatic inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {\n  MI_UNUSED(page);\n  MI_UNUSED(block);\n  return false;\n}\n#endif\n\n\n// ---------------------------------------------------------------------------\n// Check for heap block overflow by setting up padding at the end of the block\n// ---------------------------------------------------------------------------\n\n#if MI_PADDING // && !MI_TRACK_ENABLED\nstatic bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {\n  *bsize = mi_page_usable_block_size(page);\n  const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);\n  mi_track_mem_defined(padding,sizeof(mi_padding_t));\n  *delta = padding->delta;\n  uint32_t canary = padding->canary;\n  uintptr_t keys[2];\n  keys[0] = page->keys[0];\n  keys[1] = page->keys[1];\n  bool ok = (mi_ptr_encode_canary(page,block,keys) == canary && *delta <= *bsize);\n  mi_track_mem_noaccess(padding,sizeof(mi_padding_t));\n  return ok;\n}\n\n// Return the exact usable size of a block.\nstatic size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {\n  size_t bsize;\n  size_t delta;\n  bool ok = mi_page_decode_padding(page, block, &delta, &bsize);\n  mi_assert_internal(ok); mi_assert_internal(delta <= bsize);\n  return (ok ? bsize - delta : 0);\n}\n\n// When a non-thread-local block is freed, it becomes part of the thread delayed free\n// list that is freed later by the owning heap. If the exact usable size is too small to\n// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)\n// so it will later not trigger an overflow error in `mi_free_block`.\nvoid _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {\n  size_t bsize;\n  size_t delta;\n  bool ok = mi_page_decode_padding(page, block, &delta, &bsize);\n  mi_assert_internal(ok);\n  if (!ok || (bsize - delta) >= min_size) return;  // usually already enough space\n  mi_assert_internal(bsize >= min_size);\n  if (bsize < min_size) return;  // should never happen\n  size_t new_delta = (bsize - min_size);\n  mi_assert_internal(new_delta < bsize);\n  mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize);\n  mi_track_mem_defined(padding,sizeof(mi_padding_t));\n  padding->delta = (uint32_t)new_delta;\n  mi_track_mem_noaccess(padding,sizeof(mi_padding_t));\n}\n#else\nstatic size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) {\n  MI_UNUSED(block);\n  return mi_page_usable_block_size(page);\n}\n\nvoid _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {\n  MI_UNUSED(page);\n  MI_UNUSED(block);\n  MI_UNUSED(min_size);\n}\n#endif\n\n#if MI_PADDING && MI_PADDING_CHECK\n\nstatic bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {\n  size_t bsize;\n  size_t delta;\n  bool ok = mi_page_decode_padding(page, block, &delta, &bsize);\n  *size = *wrong = bsize;\n  if (!ok) return false;\n  mi_assert_internal(bsize >= delta);\n  *size = bsize - delta;\n  if (!mi_page_is_huge(page)) {\n    uint8_t* fill = (uint8_t*)block + bsize - delta;\n    const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes\n    mi_track_mem_defined(fill, maxpad);\n    for (size_t i = 0; i < maxpad; i++) {\n      if (fill[i] != MI_DEBUG_PADDING) {\n        *wrong = bsize - delta + i;\n        ok = false;\n        break;\n      }\n    }\n    mi_track_mem_noaccess(fill, maxpad);\n  }\n  return ok;\n}\n\nstatic void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {\n  size_t size;\n  size_t wrong;\n  if (!mi_verify_padding(page,block,&size,&wrong)) {\n    _mi_error_message(EFAULT, \"buffer overflow in heap block %p of size %zu: write after %zu bytes\\n\", block, size, wrong );\n  }\n}\n\n#else\n\nstatic void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {\n  MI_UNUSED(page);\n  MI_UNUSED(block);\n}\n\n#endif\n\n// only maintain stats for smaller objects if requested\n#if (MI_STAT>0)\nstatic void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {\n  MI_UNUSED(block);\n  mi_heap_t* const heap = mi_heap_get_default();\n  const size_t bsize = mi_page_usable_block_size(page);\n  // #if (MI_STAT>1)\n  // const size_t usize = mi_page_usable_size_of(page, block);\n  // mi_heap_stat_decrease(heap, malloc_requested, usize);\n  // #endif\n  if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {\n    mi_heap_stat_decrease(heap, malloc_normal, bsize);\n    #if (MI_STAT > 1)\n    mi_heap_stat_decrease(heap, malloc_bins[_mi_bin(bsize)], 1);\n    #endif\n  }\n  //else if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {\n  //  mi_heap_stat_decrease(heap, malloc_large, bsize);\n  //}\n  else {\n    mi_heap_stat_decrease(heap, malloc_huge, bsize);\n  }\n}\n#else\nstatic void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {\n  MI_UNUSED(page); MI_UNUSED(block);\n}\n#endif\n\n\n// Remove guard page when building with MI_GUARDED\n#if MI_GUARDED\nstatic void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p) {\n  MI_UNUSED(p);\n  mi_assert_internal(mi_block_ptr_is_guarded(block, p));\n  mi_assert_internal(mi_page_has_aligned(page));\n  mi_assert_internal((uint8_t*)p - (uint8_t*)block >= (ptrdiff_t)sizeof(mi_block_t));\n  mi_assert_internal(block->next == MI_BLOCK_TAG_GUARDED);\n\n  const size_t bsize = mi_page_block_size(page);\n  const size_t psize = _mi_os_page_size();\n  mi_assert_internal(bsize > psize);\n  mi_assert_internal(_mi_page_segment(page)->allow_decommit);\n  void* gpage = (uint8_t*)block + bsize - psize;\n  mi_assert_internal(_mi_is_aligned(gpage, psize));\n  _mi_os_unprotect(gpage, psize);\n}\n#endif\n"
  },
  {
    "path": "src/heap.c",
    "content": "/*----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"  // mi_prim_get_default_heap\n\n#include <string.h>  // memset, memcpy\n\n#if defined(_MSC_VER) && (_MSC_VER < 1920)\n#pragma warning(disable:4204)  // non-constant aggregate initializer\n#endif\n\n/* -----------------------------------------------------------\n  Helpers\n----------------------------------------------------------- */\n\n// return `true` if ok, `false` to break\ntypedef bool (heap_page_visitor_fun)(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2);\n\n// Visit all pages in a heap; returns `false` if break was called.\nstatic bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void* arg1, void* arg2)\n{\n  if (heap==NULL || heap->page_count==0) return 0;\n\n  // visit all pages\n  #if MI_DEBUG>1\n  size_t total = heap->page_count;\n  size_t count = 0;\n  #endif\n\n  for (size_t i = 0; i <= MI_BIN_FULL; i++) {\n    mi_page_queue_t* pq = &heap->pages[i];\n    mi_page_t* page = pq->first;\n    while(page != NULL) {\n      mi_page_t* next = page->next; // save next in case the page gets removed from the queue\n      mi_assert_internal(mi_page_heap(page) == heap);\n      #if MI_DEBUG>1\n      count++;\n      #endif\n      if (!fn(heap, pq, page, arg1, arg2)) return false;\n      page = next; // and continue\n    }\n  }\n  mi_assert_internal(count == total);\n  return true;\n}\n\n\n#if MI_DEBUG>=2\nstatic bool mi_heap_page_is_valid(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {\n  MI_UNUSED(arg1);\n  MI_UNUSED(arg2);\n  MI_UNUSED(pq);\n  mi_assert_internal(mi_page_heap(page) == heap);\n  mi_segment_t* segment = _mi_page_segment(page);\n  mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == heap->thread_id);\n  mi_assert_expensive(_mi_page_is_valid(page));\n  return true;\n}\n#endif\n#if MI_DEBUG>=3\nstatic bool mi_heap_is_valid(mi_heap_t* heap) {\n  mi_assert_internal(heap!=NULL);\n  mi_heap_visit_pages(heap, &mi_heap_page_is_valid, NULL, NULL);\n  return true;\n}\n#endif\n\n\n\n\n/* -----------------------------------------------------------\n  \"Collect\" pages by migrating `local_free` and `thread_free`\n  lists and freeing empty pages. This is done when a thread\n  stops (and in that case abandons pages if there are still\n  blocks alive)\n----------------------------------------------------------- */\n\ntypedef enum mi_collect_e {\n  MI_NORMAL,\n  MI_FORCE,\n  MI_ABANDON\n} mi_collect_t;\n\n\nstatic bool mi_heap_page_collect(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg_collect, void* arg2 ) {\n  MI_UNUSED(arg2);\n  MI_UNUSED(heap);\n  mi_assert_internal(mi_heap_page_is_valid(heap, pq, page, NULL, NULL));\n  mi_collect_t collect = *((mi_collect_t*)arg_collect);\n  _mi_page_free_collect(page, collect >= MI_FORCE);\n  if (collect == MI_FORCE) {\n    // note: call before a potential `_mi_page_free` as the segment may be freed if this was the last used page in that segment.\n    mi_segment_t* segment = _mi_page_segment(page);\n    _mi_segment_collect(segment, true /* force? */);\n  }\n  if (mi_page_all_free(page)) {\n    // no more used blocks, free the page.\n    // note: this will free retired pages as well.\n    _mi_page_free(page, pq, collect >= MI_FORCE);\n  }\n  else if (collect == MI_ABANDON) {\n    // still used blocks but the thread is done; abandon the page\n    _mi_page_abandon(page, pq);\n  }\n  return true; // don't break\n}\n\nstatic bool mi_heap_page_never_delayed_free(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {\n  MI_UNUSED(arg1);\n  MI_UNUSED(arg2);\n  MI_UNUSED(heap);\n  MI_UNUSED(pq);\n  _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);\n  return true; // don't break\n}\n\nstatic void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)\n{\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return;\n\n  const bool force = (collect >= MI_FORCE);\n  _mi_deferred_free(heap, force);\n\n  // python/cpython#112532: we may be called from a thread that is not the owner of the heap\n  const bool is_main_thread = (_mi_is_main_thread() && heap->thread_id == _mi_thread_id());\n\n  // note: never reclaim on collect but leave it to threads that need storage to reclaim\n  const bool force_main =\n    #ifdef NDEBUG\n      collect == MI_FORCE\n    #else\n      collect >= MI_FORCE\n    #endif\n      && is_main_thread && mi_heap_is_backing(heap) && !heap->no_reclaim;\n\n  if (force_main) {\n    // the main thread is abandoned (end-of-program), try to reclaim all abandoned segments.\n    // if all memory is freed by now, all segments should be freed.\n    // note: this only collects in the current subprocess\n    _mi_abandoned_reclaim_all(heap, &heap->tld->segments);\n  }\n\n  // if abandoning, mark all pages to no longer add to delayed_free\n  if (collect == MI_ABANDON) {\n    mi_heap_visit_pages(heap, &mi_heap_page_never_delayed_free, NULL, NULL);\n  }\n\n  // free all current thread delayed blocks.\n  // (if abandoning, after this there are no more thread-delayed references into the pages.)\n  _mi_heap_delayed_free_all(heap);\n\n  // collect retired pages\n  _mi_heap_collect_retired(heap, force);\n\n  // collect all pages owned by this thread\n  mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL);\n  mi_assert_internal( collect != MI_ABANDON || mi_atomic_load_ptr_acquire(mi_block_t,&heap->thread_delayed_free) == NULL );\n\n  // collect abandoned segments (in particular, purge expired parts of segments in the abandoned segment list)\n  // note: forced purge can be quite expensive if many threads are created/destroyed so we do not force on abandonment\n  _mi_abandoned_collect(heap, collect == MI_FORCE /* force? */, &heap->tld->segments);\n\n  // if forced, collect thread data cache on program-exit (or shared library unload)\n  if (force && is_main_thread && mi_heap_is_backing(heap)) {\n    _mi_thread_data_collect();  // collect thread data cache\n  }\n\n  // collect arenas (this is program wide so don't force purges on abandonment of threads)\n  _mi_arenas_collect(collect == MI_FORCE /* force purge? */);\n\n  // merge statistics\n  if (collect <= MI_FORCE) { _mi_stats_merge_thread(heap->tld); }\n}\n\nvoid _mi_heap_collect_abandon(mi_heap_t* heap) {\n  mi_heap_collect_ex(heap, MI_ABANDON);\n}\n\nvoid mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept {\n  mi_heap_collect_ex(heap, (force ? MI_FORCE : MI_NORMAL));\n}\n\nvoid mi_collect(bool force) mi_attr_noexcept {\n  mi_heap_collect(mi_prim_get_default_heap(), force);\n}\n\n\n/* -----------------------------------------------------------\n  Heap new\n----------------------------------------------------------- */\n\nmi_heap_t* mi_heap_get_default(void) {\n  mi_thread_init();\n  return mi_prim_get_default_heap();\n}\n\nstatic bool mi_heap_is_default(const mi_heap_t* heap) {\n  return (heap == mi_prim_get_default_heap());\n}\n\n\nmi_heap_t* mi_heap_get_backing(void) {\n  mi_heap_t* heap = mi_heap_get_default();\n  mi_assert_internal(heap!=NULL);\n  mi_heap_t* bheap = heap->tld->heap_backing;\n  mi_assert_internal(bheap!=NULL);\n  mi_assert_internal(bheap->thread_id == _mi_thread_id());\n  return bheap;\n}\n\nvoid _mi_heap_init(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool noreclaim, uint8_t tag) {\n  _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t));\n  heap->tld = tld;\n  heap->thread_id  = _mi_thread_id();\n  heap->arena_id   = arena_id;\n  heap->no_reclaim = noreclaim;\n  heap->tag        = tag;\n  if (heap == tld->heap_backing) {\n    #if defined(_WIN32) && !defined(MI_SHARED_LIB)\n      _mi_random_init_weak(&heap->random);    // prevent allocation failure during bcrypt dll initialization with static linking (issue #1185)\n    #else\n      _mi_random_init(&heap->random);\n    #endif\n  }\n  else {\n    _mi_random_split(&tld->heap_backing->random, &heap->random);\n  }\n  heap->cookie  = _mi_heap_random_next(heap) | 1;\n  heap->keys[0] = _mi_heap_random_next(heap);\n  heap->keys[1] = _mi_heap_random_next(heap);\n  _mi_heap_guarded_init(heap);\n  // push on the thread local heaps list\n  heap->next = heap->tld->heaps;\n  heap->tld->heaps = heap;\n}\n\nmi_decl_nodiscard mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id) {\n  mi_heap_t* bheap = mi_heap_get_backing();\n  mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t);  // todo: OS allocate in secure mode?\n  if (heap == NULL) return NULL;\n  mi_assert(heap_tag >= 0 && heap_tag < 256);\n  _mi_heap_init(heap, bheap->tld, arena_id, allow_destroy /* no reclaim? */, (uint8_t)heap_tag /* heap tag */);\n  return heap;\n}\n\nmi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {\n  return mi_heap_new_ex(0 /* default heap tag */, false /* don't allow `mi_heap_destroy` */, arena_id);\n}\n\nmi_decl_nodiscard mi_heap_t* mi_heap_new(void) {\n  // don't reclaim abandoned memory or otherwise destroy is unsafe\n  return mi_heap_new_ex(0 /* default heap tag */, true /* no reclaim */, _mi_arena_id_none());\n}\n\nbool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid) {\n  return _mi_arena_memid_is_suitable(memid, heap->arena_id);\n}\n\nuintptr_t _mi_heap_random_next(mi_heap_t* heap) {\n  return _mi_random_next(&heap->random);\n}\n\n// zero out the page queues\nstatic void mi_heap_reset_pages(mi_heap_t* heap) {\n  mi_assert_internal(heap != NULL);\n  mi_assert_internal(mi_heap_is_initialized(heap));\n  // TODO: copy full empty heap instead?\n  memset(&heap->pages_free_direct, 0, sizeof(heap->pages_free_direct));\n  _mi_memcpy_aligned(&heap->pages, &_mi_heap_empty.pages, sizeof(heap->pages));\n  heap->thread_delayed_free = NULL;\n  heap->page_count = 0;\n}\n\n// called from `mi_heap_destroy` and `mi_heap_delete` to free the internal heap resources.\nstatic void mi_heap_free(mi_heap_t* heap) {\n  mi_assert(heap != NULL);\n  mi_assert_internal(mi_heap_is_initialized(heap));\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return;\n  if (mi_heap_is_backing(heap)) return; // dont free the backing heap\n\n  // reset default\n  if (mi_heap_is_default(heap)) {\n    _mi_heap_set_default_direct(heap->tld->heap_backing);\n  }\n\n  // remove ourselves from the thread local heaps list\n  // linear search but we expect the number of heaps to be relatively small\n  mi_heap_t* prev = NULL;\n  mi_heap_t* curr = heap->tld->heaps;\n  while (curr != heap && curr != NULL) {\n    prev = curr;\n    curr = curr->next;\n  }\n  mi_assert_internal(curr == heap);\n  if (curr == heap) {\n    if (prev != NULL) { prev->next = heap->next; }\n                 else { heap->tld->heaps = heap->next; }\n  }\n  mi_assert_internal(heap->tld->heaps != NULL);\n\n  // and free the used memory\n  mi_free(heap);\n}\n\n// return a heap on the same thread as `heap` specialized for the specified tag (if it exists)\nmi_heap_t* _mi_heap_by_tag(mi_heap_t* heap, uint8_t tag) {\n  if (heap->tag == tag) {\n    return heap;\n  }\n  for (mi_heap_t *curr = heap->tld->heaps; curr != NULL; curr = curr->next) {\n    if (curr->tag == tag) {\n      return curr;\n    }\n  }\n  return NULL;\n}\n\n/* -----------------------------------------------------------\n  Heap destroy\n----------------------------------------------------------- */\n\nstatic bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {\n  MI_UNUSED(arg1);\n  MI_UNUSED(arg2);\n  MI_UNUSED(heap);\n  MI_UNUSED(pq);\n\n  // ensure no more thread_delayed_free will be added\n  _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);\n\n  // stats\n  const size_t bsize = mi_page_block_size(page);\n  if (bsize > MI_MEDIUM_OBJ_SIZE_MAX) {\n    //if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {\n    //  mi_heap_stat_decrease(heap, malloc_large, bsize);\n    //}\n    //else \n    {\n      mi_heap_stat_decrease(heap, malloc_huge, bsize);\n    }\n  }\n  #if (MI_STAT>0)\n  _mi_page_free_collect(page, false);  // update used count\n  const size_t inuse = page->used;\n  if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {\n    mi_heap_stat_decrease(heap, malloc_normal, bsize * inuse);\n    #if (MI_STAT>1)\n    mi_heap_stat_decrease(heap, malloc_bins[_mi_bin(bsize)], inuse);\n    #endif\n  }\n  // mi_heap_stat_decrease(heap, malloc_requested, bsize * inuse);  // todo: off for aligned blocks...\n  #endif\n\n  /// pretend it is all free now\n  mi_assert_internal(mi_page_thread_free(page) == NULL);\n  page->used = 0;\n\n  // and free the page\n  // mi_page_free(page,false);\n  page->next = NULL;\n  page->prev = NULL;\n  _mi_segment_page_free(page,false /* no force? */, &heap->tld->segments);\n\n  return true; // keep going\n}\n\nvoid _mi_heap_destroy_pages(mi_heap_t* heap) {\n  mi_heap_visit_pages(heap, &_mi_heap_page_destroy, NULL, NULL);\n  mi_heap_reset_pages(heap);\n}\n\n#if MI_TRACK_HEAP_DESTROY\nstatic bool mi_cdecl mi_heap_track_block_free(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg) {\n  MI_UNUSED(heap); MI_UNUSED(area);  MI_UNUSED(arg); MI_UNUSED(block_size);\n  mi_track_free_size(block,mi_usable_size(block));\n  return true;\n}\n#endif\n\nvoid mi_heap_destroy(mi_heap_t* heap) {\n  mi_assert(heap != NULL);\n  mi_assert(mi_heap_is_initialized(heap));\n  mi_assert(heap->no_reclaim);\n  mi_assert_expensive(mi_heap_is_valid(heap));\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return;\n  #if MI_GUARDED\n  // _mi_warning_message(\"'mi_heap_destroy' called but MI_GUARDED is enabled -- using `mi_heap_delete` instead (heap at %p)\\n\", heap);\n  mi_heap_delete(heap);\n  return;\n  #else\n  if (!heap->no_reclaim) {\n    _mi_warning_message(\"'mi_heap_destroy' called but ignored as the heap was not created with 'allow_destroy' (heap at %p)\\n\", heap);\n    // don't free in case it may contain reclaimed pages\n    mi_heap_delete(heap);\n  }\n  else {\n    // track all blocks as freed\n    #if MI_TRACK_HEAP_DESTROY\n    mi_heap_visit_blocks(heap, true, mi_heap_track_block_free, NULL);\n    #endif\n    // free all pages\n    _mi_heap_destroy_pages(heap);\n    mi_heap_free(heap);\n  }\n  #endif\n}\n\n// forcefully destroy all heaps in the current thread\nvoid _mi_heap_unsafe_destroy_all(mi_heap_t* heap) {\n  mi_assert_internal(heap != NULL);\n  if (heap == NULL) return;\n  mi_heap_t* curr = heap->tld->heaps;\n  while (curr != NULL) {\n    mi_heap_t* next = curr->next;\n    if (curr->no_reclaim) {\n      mi_heap_destroy(curr);\n    }\n    else {\n      _mi_heap_destroy_pages(curr);\n    }\n    curr = next;\n  }\n}\n\n/* -----------------------------------------------------------\n  Safe Heap delete\n----------------------------------------------------------- */\n\n// Transfer the pages from one heap to the other\nstatic void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) {\n  mi_assert_internal(heap!=NULL);\n  if (from==NULL || from->page_count == 0) return;\n\n  // reduce the size of the delayed frees\n  _mi_heap_delayed_free_partial(from);\n\n  // transfer all pages by appending the queues; this will set a new heap field\n  // so threads may do delayed frees in either heap for a while.\n  // note: appending waits for each page to not be in the `MI_DELAYED_FREEING` state\n  // so after this only the new heap will get delayed frees\n  for (size_t i = 0; i <= MI_BIN_FULL; i++) {\n    mi_page_queue_t* pq = &heap->pages[i];\n    mi_page_queue_t* append = &from->pages[i];\n    size_t pcount = _mi_page_queue_append(heap, pq, append);\n    heap->page_count += pcount;\n    from->page_count -= pcount;\n  }\n  mi_assert_internal(from->page_count == 0);\n\n  // and do outstanding delayed frees in the `from` heap\n  // note: be careful here as the `heap` field in all those pages no longer point to `from`,\n  // turns out to be ok as `_mi_heap_delayed_free` only visits the list and calls a\n  // the regular `_mi_free_delayed_block` which is safe.\n  _mi_heap_delayed_free_all(from);\n  #if !defined(_MSC_VER) || (_MSC_VER > 1900) // somehow the following line gives an error in VS2015, issue #353\n  mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_block_t,&from->thread_delayed_free) == NULL);\n  #endif\n\n  // and reset the `from` heap\n  mi_heap_reset_pages(from);\n}\n\n// are two heaps compatible with respect to heap-tag, exclusive arena etc.\nstatic bool mi_heaps_are_compatible(mi_heap_t* heap1, mi_heap_t* heap2) {\n  return (heap1->tag == heap2->tag &&                   // store same kind of objects\n          heap1->arena_id == heap2->arena_id);          // same arena preference\n}\n\n// Safe delete a heap without freeing any still allocated blocks in that heap.\nvoid mi_heap_delete(mi_heap_t* heap)\n{\n  mi_assert(heap != NULL);\n  mi_assert(mi_heap_is_initialized(heap));\n  mi_assert_expensive(mi_heap_is_valid(heap));\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return;\n\n  mi_heap_t* bheap = heap->tld->heap_backing;\n  if (bheap != heap && mi_heaps_are_compatible(bheap,heap)) {\n    // transfer still used pages to the backing heap\n    mi_heap_absorb(bheap, heap);\n  }\n  else {\n    // the backing heap abandons its pages\n    _mi_heap_collect_abandon(heap);\n  }\n  mi_assert_internal(heap->page_count==0);\n  mi_heap_free(heap);\n}\n\nmi_heap_t* mi_heap_set_default(mi_heap_t* heap) {\n  mi_assert(heap != NULL);\n  mi_assert(mi_heap_is_initialized(heap));\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return NULL;\n  mi_assert_expensive(mi_heap_is_valid(heap));\n  mi_heap_t* old = mi_prim_get_default_heap();\n  _mi_heap_set_default_direct(heap);\n  return old;\n}\n\n\n\n\n/* -----------------------------------------------------------\n  Analysis\n----------------------------------------------------------- */\n\n// static since it is not thread safe to access heaps from other threads.\nstatic mi_heap_t* mi_heap_of_block(const void* p) {\n  if (p == NULL) return NULL;\n  mi_segment_t* segment = _mi_ptr_segment(p);\n  bool valid = (_mi_ptr_cookie(segment) == segment->cookie);\n  mi_assert_internal(valid);\n  if mi_unlikely(!valid) return NULL;\n  return mi_page_heap(_mi_segment_page_of(segment,p));\n}\n\nbool mi_heap_contains_block(mi_heap_t* heap, const void* p) {\n  mi_assert(heap != NULL);\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return false;\n  return (heap == mi_heap_of_block(p));\n}\n\n\nstatic bool mi_heap_page_check_owned(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* p, void* vfound) {\n  MI_UNUSED(heap);\n  MI_UNUSED(pq);\n  bool* found = (bool*)vfound;\n  void* start = mi_page_start(page);\n  void* end   = (uint8_t*)start + (page->capacity * mi_page_block_size(page));\n  *found = (p >= start && p < end);\n  return (!*found); // continue if not found\n}\n\nbool mi_heap_check_owned(mi_heap_t* heap, const void* p) {\n  mi_assert(heap != NULL);\n  if (heap==NULL || !mi_heap_is_initialized(heap)) return false;\n  if (((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0) return false;  // only aligned pointers\n  bool found = false;\n  mi_heap_visit_pages(heap, &mi_heap_page_check_owned, (void*)p, &found);\n  return found;\n}\n\nbool mi_check_owned(const void* p) {\n  return mi_heap_check_owned(mi_prim_get_default_heap(), p);\n}\n\n/* -----------------------------------------------------------\n  Visit all heap blocks and areas\n  Todo: enable visiting abandoned pages, and\n        enable visiting all blocks of all heaps across threads\n----------------------------------------------------------- */\n\nvoid _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) {\n  const size_t bsize = mi_page_block_size(page);\n  const size_t ubsize = mi_page_usable_block_size(page);\n  area->reserved = page->reserved * bsize;\n  area->committed = page->capacity * bsize;\n  area->blocks = mi_page_start(page);\n  area->used = page->used;   // number of blocks in use (#553)\n  area->block_size = ubsize;\n  area->full_block_size = bsize;\n  area->heap_tag = page->heap_tag;\n}\n\n\nstatic void mi_get_fast_divisor(size_t divisor, uint64_t* magic, size_t* shift) {\n  mi_assert_internal(divisor > 0 && divisor <= UINT32_MAX);\n  *shift = MI_SIZE_BITS - mi_clz(divisor - 1);\n  *magic = ((((uint64_t)1 << 32) * (((uint64_t)1 << *shift) - divisor)) / divisor + 1);\n}\n\nstatic size_t mi_fast_divide(size_t n, uint64_t magic, size_t shift) {\n  mi_assert_internal(n <= UINT32_MAX);\n  const uint64_t hi = ((uint64_t)n * magic) >> 32;\n  return (size_t)((hi + n) >> shift);\n}\n\nbool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi_block_visit_fun* visitor, void* arg) {\n  mi_assert(area != NULL);\n  if (area==NULL) return true;\n  mi_assert(page != NULL);\n  if (page == NULL) return true;\n\n  _mi_page_free_collect(page,true);              // collect both thread_delayed and local_free\n  mi_assert_internal(page->local_free == NULL);\n  if (page->used == 0) return true;\n\n  size_t psize;\n  uint8_t* const pstart = _mi_segment_page_start(_mi_page_segment(page), page, &psize);\n  mi_heap_t* const heap = mi_page_heap(page);\n  const size_t bsize    = mi_page_block_size(page);\n  const size_t ubsize   = mi_page_usable_block_size(page); // without padding\n\n  // optimize page with one block\n  if (page->capacity == 1) {\n    mi_assert_internal(page->used == 1 && page->free == NULL);\n    return visitor(mi_page_heap(page), area, pstart, ubsize, arg);\n  }\n  mi_assert(bsize <= UINT32_MAX);\n\n  // optimize full pages\n  if (page->used == page->capacity) {\n    uint8_t* block = pstart;\n    for (size_t i = 0; i < page->capacity; i++) {\n      if (!visitor(heap, area, block, ubsize, arg)) return false;\n      block += bsize;\n    }\n    return true;\n  }\n\n  // create a bitmap of free blocks.\n  #define MI_MAX_BLOCKS   (MI_SMALL_PAGE_SIZE / sizeof(void*))\n  uintptr_t free_map[MI_MAX_BLOCKS / MI_INTPTR_BITS];\n  const uintptr_t bmapsize = _mi_divide_up(page->capacity, MI_INTPTR_BITS);\n  memset(free_map, 0, bmapsize * sizeof(intptr_t));\n  if (page->capacity % MI_INTPTR_BITS != 0) {\n    // mark left-over bits at the end as free\n    size_t shift   = (page->capacity % MI_INTPTR_BITS);\n    uintptr_t mask = (UINTPTR_MAX << shift);\n    free_map[bmapsize - 1] = mask;\n  }\n\n  // fast repeated division by the block size\n  uint64_t magic;\n  size_t   shift;\n  mi_get_fast_divisor(bsize, &magic, &shift);\n\n  #if MI_DEBUG>1\n  size_t free_count = 0;\n  #endif\n  for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {\n    #if MI_DEBUG>1\n    free_count++;\n    #endif\n    mi_assert_internal((uint8_t*)block >= pstart && (uint8_t*)block < (pstart + psize));\n    size_t offset = (uint8_t*)block - pstart;\n    mi_assert_internal(offset % bsize == 0);\n    mi_assert_internal(offset <= UINT32_MAX);\n    size_t blockidx = mi_fast_divide(offset, magic, shift);\n    mi_assert_internal(blockidx == offset / bsize);\n    mi_assert_internal(blockidx < MI_MAX_BLOCKS);\n    size_t bitidx = (blockidx / MI_INTPTR_BITS);\n    size_t bit = blockidx - (bitidx * MI_INTPTR_BITS);\n    free_map[bitidx] |= ((uintptr_t)1 << bit);\n  }\n  mi_assert_internal(page->capacity == (free_count + page->used));\n\n  // walk through all blocks skipping the free ones\n  #if MI_DEBUG>1\n  size_t used_count = 0;\n  #endif\n  uint8_t* block = pstart;\n  for (size_t i = 0; i < bmapsize; i++) {\n    if (free_map[i] == 0) {\n      // every block is in use\n      for (size_t j = 0; j < MI_INTPTR_BITS; j++) {\n        #if MI_DEBUG>1\n        used_count++;\n        #endif\n        if (!visitor(heap, area, block, ubsize, arg)) return false;\n        block += bsize;\n      }\n    }\n    else {\n      // visit the used blocks in the mask\n      uintptr_t m = ~free_map[i];\n      while (m != 0) {\n        #if MI_DEBUG>1\n        used_count++;\n        #endif\n        size_t bitidx = mi_ctz(m);\n        if (!visitor(heap, area, block + (bitidx * bsize), ubsize, arg)) return false;\n        m &= m - 1;  // clear least significant bit\n      }\n      block += bsize * MI_INTPTR_BITS;\n    }\n  }\n  mi_assert_internal(page->used == used_count);\n  return true;\n}\n\n\n\n// Separate struct to keep `mi_page_t` out of the public interface\ntypedef struct mi_heap_area_ex_s {\n  mi_heap_area_t area;\n  mi_page_t* page;\n} mi_heap_area_ex_t;\n\ntypedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg);\n\nstatic bool mi_heap_visit_areas_page(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* vfun, void* arg) {\n  MI_UNUSED(heap);\n  MI_UNUSED(pq);\n  mi_heap_area_visit_fun* fun = (mi_heap_area_visit_fun*)vfun;\n  mi_heap_area_ex_t xarea;\n  xarea.page = page;\n  _mi_heap_area_init(&xarea.area, page);\n  return fun(heap, &xarea, arg);\n}\n\n// Visit all heap pages as areas\nstatic bool mi_heap_visit_areas(const mi_heap_t* heap, mi_heap_area_visit_fun* visitor, void* arg) {\n  if (visitor == NULL) return false;\n  return mi_heap_visit_pages((mi_heap_t*)heap, &mi_heap_visit_areas_page, (void*)(visitor), arg); // note: function pointer to void* :-{\n}\n\n// Just to pass arguments\ntypedef struct mi_visit_blocks_args_s {\n  bool  visit_blocks;\n  mi_block_visit_fun* visitor;\n  void* arg;\n} mi_visit_blocks_args_t;\n\nstatic bool mi_heap_area_visitor(const mi_heap_t* heap, const mi_heap_area_ex_t* xarea, void* arg) {\n  mi_visit_blocks_args_t* args = (mi_visit_blocks_args_t*)arg;\n  if (!args->visitor(heap, &xarea->area, NULL, xarea->area.block_size, args->arg)) return false;\n  if (args->visit_blocks) {\n    return _mi_heap_area_visit_blocks(&xarea->area, xarea->page, args->visitor, args->arg);\n  }\n  else {\n    return true;\n  }\n}\n\n// Visit all blocks in a heap\nbool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {\n  mi_visit_blocks_args_t args = { visit_blocks, visitor, arg };\n  return mi_heap_visit_areas(heap, &mi_heap_area_visitor, &args);\n}\n"
  },
  {
    "path": "src/init.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2022, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"\n\n#include <string.h>  // memcpy, memset\n#include <stdlib.h>  // atexit\n\n\n// Empty page used to initialize the small free pages array\nconst mi_page_t _mi_page_empty = {\n  0,\n  false, false, false, false,\n  0,       // capacity\n  0,       // reserved capacity\n  { 0 },   // flags\n  false,   // is_zero\n  0,       // retire_expire\n  NULL,    // free\n  NULL,    // local_free\n  0,       // used\n  0,       // block size shift\n  0,       // heap tag\n  0,       // block_size\n  NULL,    // page_start\n  #if (MI_PADDING || MI_ENCODE_FREELIST)\n  { 0, 0 },\n  #endif\n  MI_ATOMIC_VAR_INIT(0), // xthread_free\n  MI_ATOMIC_VAR_INIT(0), // xheap\n  NULL, NULL\n  , { 0 }  // padding\n};\n\n#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)\n\n#if (MI_SMALL_WSIZE_MAX==128)\n#if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8)\n#define MI_SMALL_PAGES_EMPTY  { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }\n#elif (MI_PADDING>0)\n#define MI_SMALL_PAGES_EMPTY  { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() }\n#else\n#define MI_SMALL_PAGES_EMPTY  { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() }\n#endif\n#else\n#error \"define right initialization sizes corresponding to MI_SMALL_WSIZE_MAX\"\n#endif\n\n// Empty page queues for every bin\n#define QNULL(sz)  { NULL, NULL, (sz)*sizeof(uintptr_t) }\n#define MI_PAGE_QUEUES_EMPTY \\\n  { QNULL(1), \\\n    QNULL(     1), QNULL(     2), QNULL(     3), QNULL(     4), QNULL(     5), QNULL(     6), QNULL(     7), QNULL(     8), /* 8 */ \\\n    QNULL(    10), QNULL(    12), QNULL(    14), QNULL(    16), QNULL(    20), QNULL(    24), QNULL(    28), QNULL(    32), /* 16 */ \\\n    QNULL(    40), QNULL(    48), QNULL(    56), QNULL(    64), QNULL(    80), QNULL(    96), QNULL(   112), QNULL(   128), /* 24 */ \\\n    QNULL(   160), QNULL(   192), QNULL(   224), QNULL(   256), QNULL(   320), QNULL(   384), QNULL(   448), QNULL(   512), /* 32 */ \\\n    QNULL(   640), QNULL(   768), QNULL(   896), QNULL(  1024), QNULL(  1280), QNULL(  1536), QNULL(  1792), QNULL(  2048), /* 40 */ \\\n    QNULL(  2560), QNULL(  3072), QNULL(  3584), QNULL(  4096), QNULL(  5120), QNULL(  6144), QNULL(  7168), QNULL(  8192), /* 48 */ \\\n    QNULL( 10240), QNULL( 12288), QNULL( 14336), QNULL( 16384), QNULL( 20480), QNULL( 24576), QNULL( 28672), QNULL( 32768), /* 56 */ \\\n    QNULL( 40960), QNULL( 49152), QNULL( 57344), QNULL( 65536), QNULL( 81920), QNULL( 98304), QNULL(114688), QNULL(131072), /* 64 */ \\\n    QNULL(163840), QNULL(196608), QNULL(229376), QNULL(262144), QNULL(327680), QNULL(393216), QNULL(458752), QNULL(524288), /* 72 */ \\\n    QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 1  /* 655360, Huge queue */), \\\n    QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 2) /* Full queue */ }\n\n#define MI_STAT_COUNT_NULL()  {0,0,0}\n\n// Empty statistics\n#define MI_STATS_NULL  \\\n  MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \\\n  { 0 }, { 0 }, \\\n  MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \\\n  MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \\\n  { 0 }, { 0 }, { 0 }, { 0 }, \\\n  { 0 }, { 0 }, { 0 }, { 0 }, \\\n  \\\n  { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, \\\n  MI_INIT4(MI_STAT_COUNT_NULL), \\\n  { 0 }, { 0 }, { 0 }, { 0 },  \\\n  \\\n  { MI_INIT4(MI_STAT_COUNT_NULL) }, \\\n  { { 0 }, { 0 }, { 0 }, { 0 } }, \\\n  \\\n  { MI_INIT74(MI_STAT_COUNT_NULL) }, \\\n  { MI_INIT74(MI_STAT_COUNT_NULL) }\n\n\n// Empty slice span queues for every bin\n#define SQNULL(sz)  { NULL, NULL, sz }\n#define MI_SEGMENT_SPAN_QUEUES_EMPTY \\\n  { SQNULL(1), \\\n    SQNULL(     1), SQNULL(     2), SQNULL(     3), SQNULL(     4), SQNULL(     5), SQNULL(     6), SQNULL(     7), SQNULL(    10), /*  8 */ \\\n    SQNULL(    12), SQNULL(    14), SQNULL(    16), SQNULL(    20), SQNULL(    24), SQNULL(    28), SQNULL(    32), SQNULL(    40), /* 16 */ \\\n    SQNULL(    48), SQNULL(    56), SQNULL(    64), SQNULL(    80), SQNULL(    96), SQNULL(   112), SQNULL(   128), SQNULL(   160), /* 24 */ \\\n    SQNULL(   192), SQNULL(   224), SQNULL(   256), SQNULL(   320), SQNULL(   384), SQNULL(   448), SQNULL(   512), SQNULL(   640), /* 32 */ \\\n    SQNULL(   768), SQNULL(   896), SQNULL(  1024) /* 35 */ }\n\n\n// --------------------------------------------------------\n// Statically allocate an empty heap as the initial\n// thread local value for the default heap,\n// and statically allocate the backing heap for the main\n// thread so it can function without doing any allocation\n// itself (as accessing a thread local for the first time\n// may lead to allocation itself on some platforms)\n// --------------------------------------------------------\n\nmi_decl_cache_align const mi_heap_t _mi_heap_empty = {\n  NULL,\n  MI_ATOMIC_VAR_INIT(NULL),\n  0,                // tid\n  0,                // cookie\n  0,                // arena id\n  { 0, 0 },         // keys\n  { {0}, {0}, 0, true }, // random\n  0,                // page count\n  MI_BIN_FULL, 0,   // page retired min/max\n  0, 0,             // generic count\n  NULL,             // next\n  false,            // can reclaim\n  0,                // tag\n  #if MI_GUARDED\n  0, 0, 0, 1,       // count is 1 so we never write to it (see `internal.h:mi_heap_malloc_use_guarded`)\n  #endif\n  MI_SMALL_PAGES_EMPTY,\n  MI_PAGE_QUEUES_EMPTY\n};\n\nstatic mi_decl_cache_align mi_subproc_t mi_subproc_default;\n\n#define tld_empty_stats  ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats)))\n\nmi_decl_cache_align static const mi_tld_t tld_empty = {\n  0,\n  false,\n  NULL, NULL,\n  { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, tld_empty_stats }, // segments\n  { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL }       // stats\n};\n\nmi_threadid_t _mi_thread_id(void) mi_attr_noexcept {\n  return _mi_prim_thread_id();\n}\n\n// the thread-local default heap for allocation\nmi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty;\n\nextern mi_decl_hidden mi_heap_t _mi_heap_main;\n\nstatic mi_decl_cache_align mi_tld_t tld_main = {\n  0, false,\n  &_mi_heap_main, & _mi_heap_main,\n  { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, &tld_main.stats }, // segments\n  { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL }       // stats\n};\n\nmi_decl_cache_align mi_heap_t _mi_heap_main = {\n  &tld_main,\n  MI_ATOMIC_VAR_INIT(NULL),\n  0,                // thread id\n  0,                // initial cookie\n  0,                // arena id\n  { 0, 0 },         // the key of the main heap can be fixed (unlike page keys that need to be secure!)\n  { {0x846ca68b}, {0}, 0, true },  // random\n  0,                // page count\n  MI_BIN_FULL, 0,   // page retired min/max\n  0, 0,             // generic count\n  NULL,             // next heap\n  false,            // can reclaim\n  0,                // tag\n  #if MI_GUARDED\n  0, 0, 0, 0,\n  #endif\n  MI_SMALL_PAGES_EMPTY,\n  MI_PAGE_QUEUES_EMPTY\n};\n\nbool _mi_process_is_initialized = false;  // set to `true` in `mi_process_init`.\n\nmi_stats_t _mi_stats_main = { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL };\n\n#if MI_GUARDED\nmi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {\n  heap->guarded_sample_rate  = sample_rate;\n  heap->guarded_sample_count = sample_rate;  // count down samples\n  if (heap->guarded_sample_rate > 1) {\n    if (seed == 0) {\n      seed = _mi_heap_random_next(heap);\n    }\n    heap->guarded_sample_count = (seed % heap->guarded_sample_rate) + 1;  // start at random count between 1 and `sample_rate`\n  }\n}\n\nmi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) {\n  heap->guarded_size_min = min;\n  heap->guarded_size_max = (min > max ? min : max);\n}\n\nvoid _mi_heap_guarded_init(mi_heap_t* heap) {\n  mi_heap_guarded_set_sample_rate(heap,\n    (size_t)mi_option_get_clamp(mi_option_guarded_sample_rate, 0, LONG_MAX),\n    (size_t)mi_option_get(mi_option_guarded_sample_seed));\n  mi_heap_guarded_set_size_bound(heap,\n    (size_t)mi_option_get_clamp(mi_option_guarded_min, 0, LONG_MAX),\n    (size_t)mi_option_get_clamp(mi_option_guarded_max, 0, LONG_MAX) );\n}\n#else\nmi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {\n  MI_UNUSED(heap); MI_UNUSED(sample_rate); MI_UNUSED(seed);\n}\n\nmi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) {\n  MI_UNUSED(heap); MI_UNUSED(min); MI_UNUSED(max);\n}\nvoid _mi_heap_guarded_init(mi_heap_t* heap) {\n  MI_UNUSED(heap);\n}\n#endif\n\n\nstatic void mi_heap_main_init(void) {\n  if (_mi_heap_main.cookie == 0) {\n    _mi_heap_main.thread_id = _mi_thread_id();\n    _mi_heap_main.cookie = 1;\n    #if defined(_WIN32) && !defined(MI_SHARED_LIB)\n      _mi_random_init_weak(&_mi_heap_main.random);    // prevent allocation failure during bcrypt dll initialization with static linking\n    #else\n      _mi_random_init(&_mi_heap_main.random);\n    #endif\n    _mi_heap_main.cookie  = _mi_heap_random_next(&_mi_heap_main);\n    _mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main);\n    _mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main);\n    mi_lock_init(&mi_subproc_default.abandoned_os_lock);\n    mi_lock_init(&mi_subproc_default.abandoned_os_visit_lock);\n    _mi_heap_guarded_init(&_mi_heap_main);\n  }\n}\n\nmi_heap_t* _mi_heap_main_get(void) {\n  mi_heap_main_init();\n  return &_mi_heap_main;\n}\n\n/* -----------------------------------------------------------\n  Sub process\n----------------------------------------------------------- */\n\nmi_subproc_id_t mi_subproc_main(void) {\n  return NULL;\n}\n\nmi_subproc_id_t mi_subproc_new(void) {\n  mi_memid_t memid = _mi_memid_none();\n  mi_subproc_t* subproc = (mi_subproc_t*)_mi_arena_meta_zalloc(sizeof(mi_subproc_t), &memid);\n  if (subproc == NULL) return NULL;\n  subproc->memid = memid;\n  subproc->abandoned_os_list = NULL;\n  mi_lock_init(&subproc->abandoned_os_lock);\n  mi_lock_init(&subproc->abandoned_os_visit_lock);\n  return subproc;\n}\n\nmi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) {\n  return (subproc_id == NULL ? &mi_subproc_default : (mi_subproc_t*)subproc_id);\n}\n\nvoid mi_subproc_delete(mi_subproc_id_t subproc_id) {\n  if (subproc_id == NULL) return;\n  mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);\n  // check if there are no abandoned segments still..\n  bool safe_to_delete = false;\n  mi_lock(&subproc->abandoned_os_lock) {\n    if (subproc->abandoned_os_list == NULL) {\n      safe_to_delete = true;\n    }\n  }\n  if (!safe_to_delete) return;\n  // safe to release\n  // todo: should we refcount subprocesses?\n  mi_lock_done(&subproc->abandoned_os_lock);\n  mi_lock_done(&subproc->abandoned_os_visit_lock);\n  _mi_arena_meta_free(subproc, subproc->memid, sizeof(mi_subproc_t));\n}\n\nvoid mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) {\n  mi_heap_t* heap = mi_heap_get_default();\n  if (heap == NULL) return;\n  mi_assert(heap->tld->segments.subproc == &mi_subproc_default);\n  if (heap->tld->segments.subproc != &mi_subproc_default) return;\n  heap->tld->segments.subproc = _mi_subproc_from_id(subproc_id);\n}\n\n\n\n/* -----------------------------------------------------------\n  Initialization and freeing of the thread local heaps\n----------------------------------------------------------- */\n\n// note: in x64 in release build `sizeof(mi_thread_data_t)` is under 4KiB (= OS page size).\ntypedef struct mi_thread_data_s {\n  mi_heap_t  heap;   // must come first due to cast in `_mi_heap_done`\n  mi_tld_t   tld;\n  mi_memid_t memid;  // must come last due to zero'ing\n} mi_thread_data_t;\n\n\n// Thread meta-data is allocated directly from the OS. For\n// some programs that do not use thread pools and allocate and\n// destroy many OS threads, this may causes too much overhead\n// per thread so we maintain a small cache of recently freed metadata.\n\n#define TD_CACHE_SIZE (32)\nstatic _Atomic(mi_thread_data_t*) td_cache[TD_CACHE_SIZE];\n\nstatic mi_thread_data_t* mi_thread_data_zalloc(void) {\n  // try to find thread metadata in the cache\n  mi_thread_data_t* td = NULL;\n  for (int i = 0; i < TD_CACHE_SIZE; i++) {\n    td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]);\n    if (td != NULL) {\n      // found cached allocation, try use it\n      td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL);\n      if (td != NULL) {\n        _mi_memzero(td, offsetof(mi_thread_data_t,memid));\n        return td;\n      }\n    }\n  }\n\n  // if that fails, allocate as meta data\n  mi_memid_t memid;\n  td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid);\n  if (td == NULL) {\n    // if this fails, try once more. (issue #257)\n    td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid);\n    if (td == NULL) {\n      // really out of memory\n      _mi_error_message(ENOMEM, \"unable to allocate thread local heap metadata (%zu bytes)\\n\", sizeof(mi_thread_data_t));\n      return NULL;\n    }\n  }\n  td->memid = memid;\n  return td;\n}\n\nstatic void mi_thread_data_free( mi_thread_data_t* tdfree ) {\n  // try to add the thread metadata to the cache\n  for (int i = 0; i < TD_CACHE_SIZE; i++) {\n    mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]);\n    if (td == NULL) {\n      mi_thread_data_t* expected = NULL;\n      if (mi_atomic_cas_ptr_weak_acq_rel(mi_thread_data_t, &td_cache[i], &expected, tdfree)) {\n        return;\n      }\n    }\n  }\n  // if that fails, just free it directly\n  _mi_os_free(tdfree, sizeof(mi_thread_data_t), tdfree->memid);\n}\n\nvoid _mi_thread_data_collect(void) {\n  // free all thread metadata from the cache\n  for (int i = 0; i < TD_CACHE_SIZE; i++) {\n    mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]);\n    if (td != NULL) {\n      td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL);\n      if (td != NULL) {\n        _mi_os_free(td, sizeof(mi_thread_data_t), td->memid);\n      }\n    }\n  }\n}\n\n// Initialize the thread local default heap, called from `mi_thread_init`\nstatic bool _mi_thread_heap_init(void) {\n  if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true;\n  if (_mi_is_main_thread()) {\n    // mi_assert_internal(_mi_heap_main.thread_id != 0);  // can happen on freeBSD where alloc is called before any initialization\n    // the main heap is statically allocated\n    mi_heap_main_init();\n    _mi_heap_set_default_direct(&_mi_heap_main);\n    //mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_prim_get_default_heap());\n  }\n  else {\n    // use `_mi_os_alloc` to allocate directly from the OS\n    mi_thread_data_t* td = mi_thread_data_zalloc();\n    if (td == NULL) return false;\n\n    mi_tld_t*  tld = &td->tld;\n    mi_heap_t* heap = &td->heap;\n    _mi_tld_init(tld, heap);  // must be before `_mi_heap_init`\n    _mi_heap_init(heap, tld, _mi_arena_id_none(), false /* can reclaim */, 0 /* default tag */);\n    _mi_heap_set_default_direct(heap);\n  }\n  return false;\n}\n\n// initialize thread local data\nvoid _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) {\n  _mi_memcpy_aligned(tld, &tld_empty, sizeof(mi_tld_t));\n  tld->heap_backing = bheap;\n  tld->heaps = NULL;\n  tld->segments.subproc = &mi_subproc_default;\n  tld->segments.stats = &tld->stats;\n}\n\n// Free the thread local default heap (called from `mi_thread_done`)\nstatic bool _mi_thread_heap_done(mi_heap_t* heap) {\n  if (!mi_heap_is_initialized(heap)) return true;\n\n  // reset default heap\n  _mi_heap_set_default_direct(_mi_is_main_thread() ? &_mi_heap_main : (mi_heap_t*)&_mi_heap_empty);\n\n  // switch to backing heap\n  heap = heap->tld->heap_backing;\n  if (!mi_heap_is_initialized(heap)) return false;\n\n  // delete all non-backing heaps in this thread\n  mi_heap_t* curr = heap->tld->heaps;\n  while (curr != NULL) {\n    mi_heap_t* next = curr->next; // save `next` as `curr` will be freed\n    if (curr != heap) {\n      mi_assert_internal(!mi_heap_is_backing(curr));\n      mi_heap_delete(curr);\n    }\n    curr = next;\n  }\n  mi_assert_internal(heap->tld->heaps == heap && heap->next == NULL);\n  mi_assert_internal(mi_heap_is_backing(heap));\n\n  // collect if not the main thread\n  if (heap != &_mi_heap_main) {\n    _mi_heap_collect_abandon(heap);\n  }\n\n  // merge stats\n  _mi_stats_done(&heap->tld->stats);\n\n  // free if not the main thread\n  if (heap != &_mi_heap_main) {\n    // the following assertion does not always hold for huge segments as those are always treated\n    // as abondened: one may allocate it in one thread, but deallocate in another in which case\n    // the count can be too large or negative. todo: perhaps not count huge segments? see issue #363\n    // mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id());\n    mi_thread_data_free((mi_thread_data_t*)heap);\n  }\n  else {\n    #if 0\n    // never free the main thread even in debug mode; if a dll is linked statically with mimalloc,\n    // there may still be delete/free calls after the mi_fls_done is called. Issue #207\n    _mi_heap_destroy_pages(heap);\n    mi_assert_internal(heap->tld->heap_backing == &_mi_heap_main);\n    #endif\n  }\n  return false;\n}\n\n\n\n// --------------------------------------------------------\n// Try to run `mi_thread_done()` automatically so any memory\n// owned by the thread but not yet released can be abandoned\n// and re-owned by another thread.\n//\n// 1. windows dynamic library:\n//     call from DllMain on DLL_THREAD_DETACH\n// 2. windows static library:\n//     use `FlsAlloc` to call a destructor when the thread is done\n// 3. unix, pthreads:\n//     use a pthread key to call a destructor when a pthread is done\n//\n// In the last two cases we also need to call `mi_process_init`\n// to set up the thread local keys.\n// --------------------------------------------------------\n\n// Set up handlers so `mi_thread_done` is called automatically\nstatic void mi_process_setup_auto_thread_done(void) {\n  static bool tls_initialized = false; // fine if it races\n  if (tls_initialized) return;\n  tls_initialized = true;\n  _mi_prim_thread_init_auto_done();\n  _mi_heap_set_default_direct(&_mi_heap_main);\n}\n\n\nbool _mi_is_main_thread(void) {\n  return (_mi_heap_main.thread_id==0 || _mi_heap_main.thread_id == _mi_thread_id());\n}\n\nstatic _Atomic(size_t) thread_count = MI_ATOMIC_VAR_INIT(1);\n\nsize_t  _mi_current_thread_count(void) {\n  return mi_atomic_load_relaxed(&thread_count);\n}\n\n// This is called from the `mi_malloc_generic`\nvoid mi_thread_init(void) mi_attr_noexcept\n{\n  // ensure our process has started already\n  mi_process_init();\n\n  // initialize the thread local default heap\n  // (this will call `_mi_heap_set_default_direct` and thus set the\n  //  fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called)\n  if (_mi_thread_heap_init()) return;  // returns true if already initialized\n\n  _mi_stat_increase(&_mi_stats_main.threads, 1);\n  mi_atomic_increment_relaxed(&thread_count);\n  //_mi_verbose_message(\"thread init: 0x%zx\\n\", _mi_thread_id());\n}\n\nvoid mi_thread_done(void) mi_attr_noexcept {\n  _mi_thread_done(NULL);\n}\n\nvoid _mi_thread_done(mi_heap_t* heap)\n{\n  // calling with NULL implies using the default heap\n  if (heap == NULL) {\n    heap = mi_prim_get_default_heap();\n    if (heap == NULL) return;\n  }\n\n  // prevent re-entrancy through heap_done/heap_set_default_direct (issue #699)\n  if (!mi_heap_is_initialized(heap)) {\n    return;\n  }\n\n  // adjust stats\n  mi_atomic_decrement_relaxed(&thread_count);\n  _mi_stat_decrease(&_mi_stats_main.threads, 1);\n\n  // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps...\n  if (heap->thread_id != _mi_thread_id()) return;\n\n  // abandon the thread local heap\n  if (_mi_thread_heap_done(heap)) return;  // returns true if already ran\n}\n\nvoid _mi_heap_set_default_direct(mi_heap_t* heap)  {\n  mi_assert_internal(heap != NULL);\n  #if defined(MI_TLS_SLOT)\n  mi_prim_tls_slot_set(MI_TLS_SLOT,heap);\n  #elif defined(MI_TLS_PTHREAD_SLOT_OFS)\n  *mi_prim_tls_pthread_heap_slot() = heap;\n  #elif defined(MI_TLS_PTHREAD)\n  // we use _mi_heap_default_key\n  #else\n  _mi_heap_default = heap;\n  #endif\n\n  // ensure the default heap is passed to `_mi_thread_done`\n  // setting to a non-NULL value also ensures `mi_thread_done` is called.\n  _mi_prim_thread_associate_default_heap(heap);\n}\n\nvoid mi_thread_set_in_threadpool(void) mi_attr_noexcept {\n  // nothing\n}\n\n// --------------------------------------------------------\n// Run functions on process init/done, and thread init/done\n// --------------------------------------------------------\nstatic bool os_preloading = true;    // true until this module is initialized\n\n// Returns true if this module has not been initialized; Don't use C runtime routines until it returns false.\nbool mi_decl_noinline _mi_preloading(void) {\n  return os_preloading;\n}\n\n// Returns true if mimalloc was redirected\nmi_decl_nodiscard bool mi_is_redirected(void) mi_attr_noexcept {\n  return _mi_is_redirected();\n}\n\n// Called once by the process loader from `src/prim/prim.c`\nvoid _mi_auto_process_init(void) {\n  mi_heap_main_init();\n  #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)\n  volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true;\n  if (dummy == NULL) return;                    // use dummy or otherwise the access may get optimized away (issue #697)\n  #endif\n  os_preloading = false;\n  mi_assert_internal(_mi_is_main_thread());\n  _mi_options_init();\n  mi_process_setup_auto_thread_done();\n  mi_process_init();\n  if (_mi_is_redirected()) _mi_verbose_message(\"malloc is redirected.\\n\");\n\n  // show message from the redirector (if present)\n  const char* msg = NULL;\n  _mi_allocator_init(&msg);\n  if (msg != NULL && (mi_option_is_enabled(mi_option_verbose) || mi_option_is_enabled(mi_option_show_errors))) {\n    _mi_fputs(NULL,NULL,NULL,msg);\n  }\n\n  // reseed random\n  _mi_random_reinit_if_weak(&_mi_heap_main.random);\n}\n\n#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))\n#include <intrin.h>\nmi_decl_cache_align bool _mi_cpu_has_fsrm = false;\nmi_decl_cache_align bool _mi_cpu_has_erms = false;\n\nstatic void mi_detect_cpu_features(void) {\n  // FSRM for fast short rep movsb/stosb support (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017))\n  // EMRS for fast enhanced rep movsb/stosb support\n  int32_t cpu_info[4];\n  __cpuid(cpu_info, 7);\n  _mi_cpu_has_fsrm = ((cpu_info[3] & (1 << 4)) != 0); // bit 4 of EDX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features>\n  _mi_cpu_has_erms = ((cpu_info[1] & (1 << 9)) != 0); // bit 9 of EBX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features>\n}\n#else\nstatic void mi_detect_cpu_features(void) {\n  // nothing\n}\n#endif\n\n// Initialize the process; called by thread_init or the process loader\nvoid mi_process_init(void) mi_attr_noexcept {\n  // ensure we are called once\n  static mi_atomic_once_t process_init;\n\t#if _MSC_VER < 1920\n\tmi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main\n\t#endif\n  if (!mi_atomic_once(&process_init)) return;\n  _mi_process_is_initialized = true;\n  _mi_verbose_message(\"process init: 0x%zx\\n\", _mi_thread_id());\n  mi_process_setup_auto_thread_done();\n\n  mi_detect_cpu_features();\n  _mi_os_init();\n  mi_heap_main_init();\n  mi_thread_init();\n\n  #if defined(_WIN32)\n  // On windows, when building as a static lib the FLS cleanup happens to early for the main thread.\n  // To avoid this, set the FLS value for the main thread to NULL so the fls cleanup\n  // will not call _mi_thread_done on the (still executing) main thread. See issue #508.\n  _mi_prim_thread_associate_default_heap(NULL);\n  #endif\n\n  mi_stats_reset();  // only call stat reset *after* thread init (or the heap tld == NULL)\n  mi_track_init();\n\n  if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {\n    size_t pages = mi_option_get_clamp(mi_option_reserve_huge_os_pages, 0, 128*1024);\n    int reserve_at  = (int)mi_option_get_clamp(mi_option_reserve_huge_os_pages_at, -1, INT_MAX);\n    if (reserve_at != -1) {\n      mi_reserve_huge_os_pages_at(pages, reserve_at, pages*500);\n    } else {\n      mi_reserve_huge_os_pages_interleave(pages, 0, pages*500);\n    }\n  }\n  if (mi_option_is_enabled(mi_option_reserve_os_memory)) {\n    long ksize = mi_option_get(mi_option_reserve_os_memory);\n    if (ksize > 0) {\n      mi_reserve_os_memory((size_t)ksize*MI_KiB, true /* commit? */, true /* allow large pages? */);\n    }\n  }\n}\n\n// Called when the process is done (cdecl as it is used with `at_exit` on some platforms)\nvoid mi_cdecl mi_process_done(void) mi_attr_noexcept {\n  // only shutdown if we were initialized\n  if (!_mi_process_is_initialized) return;\n  // ensure we are called once\n  static bool process_done = false;\n  if (process_done) return;\n  process_done = true;\n\n  // get the default heap so we don't need to acces thread locals anymore\n  mi_heap_t* heap = mi_prim_get_default_heap();  // use prim to not initialize any heap\n  mi_assert_internal(heap != NULL);\n\n  // release any thread specific resources and ensure _mi_thread_done is called on all but the main thread\n  _mi_prim_thread_done_auto_done();\n\n\n  #ifndef MI_SKIP_COLLECT_ON_EXIT\n    #if (MI_DEBUG || !defined(MI_SHARED_LIB))\n    // free all memory if possible on process exit. This is not needed for a stand-alone process\n    // but should be done if mimalloc is statically linked into another shared library which\n    // is repeatedly loaded/unloaded, see issue #281.\n    mi_heap_collect(heap, true /* force */ );\n    #endif\n  #endif\n\n  // Forcefully release all retained memory; this can be dangerous in general if overriding regular malloc/free\n  // since after process_done there might still be other code running that calls `free` (like at_exit routines,\n  // or C-runtime termination code.\n  if (mi_option_is_enabled(mi_option_destroy_on_exit)) {\n    mi_heap_collect(heap, true /* force */);\n    _mi_heap_unsafe_destroy_all(heap);     // forcefully release all memory held by all heaps (of this thread only!)\n    _mi_arena_unsafe_destroy_all();\n    _mi_segment_map_unsafe_destroy();\n  }\n\n  if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) {\n    mi_stats_print(NULL);\n  }\n  _mi_allocator_done();\n  _mi_verbose_message(\"process done: 0x%zx\\n\", _mi_heap_main.thread_id);\n  os_preloading = true; // don't call the C runtime anymore\n}\n\nvoid mi_cdecl _mi_auto_process_done(void) mi_attr_noexcept {\n  if (_mi_option_get_fast(mi_option_destroy_on_exit)>1) return;\n  mi_process_done();\n}\n"
  },
  {
    "path": "src/libc.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// --------------------------------------------------------\n// This module defines various std libc functions to reduce\n// the dependency on libc, and also prevent errors caused\n// by some libc implementations when called before `main`\n// executes (due to malloc redirection)\n// --------------------------------------------------------\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"      // mi_prim_getenv\n\nchar _mi_toupper(char c) {\n  if (c >= 'a' && c <= 'z') return (c - 'a' + 'A');\n                       else return c;\n}\n\nint _mi_strnicmp(const char* s, const char* t, size_t n) {\n  if (n == 0) return 0;\n  for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) {\n    if (_mi_toupper(*s) != _mi_toupper(*t)) break;\n  }\n  return (n == 0 ? 0 : *s - *t);\n}\n\nvoid _mi_strlcpy(char* dest, const char* src, size_t dest_size) {\n  if (dest==NULL || src==NULL || dest_size == 0) return;\n  // copy until end of src, or when dest is (almost) full\n  while (*src != 0 && dest_size > 1) {\n    *dest++ = *src++;\n    dest_size--;\n  }\n  // always zero terminate\n  *dest = 0;\n}\n\nvoid _mi_strlcat(char* dest, const char* src, size_t dest_size) {\n  if (dest==NULL || src==NULL || dest_size == 0) return;\n  // find end of string in the dest buffer\n  while (*dest != 0 && dest_size > 1) {\n    dest++;\n    dest_size--;\n  }\n  // and catenate\n  _mi_strlcpy(dest, src, dest_size);\n}\n\nsize_t _mi_strlen(const char* s) {\n  if (s==NULL) return 0;\n  size_t len = 0;\n  while(s[len] != 0) { len++; }\n  return len;\n}\n\nsize_t _mi_strnlen(const char* s, size_t max_len) {\n  if (s==NULL) return 0;\n  size_t len = 0;\n  while(s[len] != 0 && len < max_len) { len++; }\n  return len;\n}\n\n#ifdef MI_NO_GETENV\nbool _mi_getenv(const char* name, char* result, size_t result_size) {\n  MI_UNUSED(name);\n  MI_UNUSED(result);\n  MI_UNUSED(result_size);\n  return false;\n}\n#else\nbool _mi_getenv(const char* name, char* result, size_t result_size) {\n  if (name==NULL || result == NULL || result_size < 64) return false;\n  return _mi_prim_getenv(name,result,result_size);\n}\n#endif\n\n// --------------------------------------------------------\n// Define our own limited `_mi_vsnprintf` and `_mi_snprintf`\n// This is mostly to avoid calling these when libc is not yet\n// initialized (and to reduce dependencies)\n//\n// format:      d i, p x u, s\n// prec:        z l ll L\n// width:       10\n// align-left:  -\n// fill:        0\n// plus:        +\n// --------------------------------------------------------\n\nstatic void mi_outc(char c, char** out, char* end) {\n  char* p = *out;\n  if (p >= end) return;\n  *p = c;\n  *out = p + 1;\n}\n\nstatic void mi_outs(const char* s, char** out, char* end) {\n  if (s == NULL) return;\n  char* p = *out;\n  while (*s != 0 && p < end) {\n    *p++ = *s++;\n  }\n  *out = p;\n}\n\nstatic void mi_out_fill(char fill, size_t len, char** out, char* end) {\n  char* p = *out;\n  for (size_t i = 0; i < len && p < end; i++) {\n    *p++ = fill;\n  }\n  *out = p;\n}\n\nstatic void mi_out_alignright(char fill, char* start, size_t len, size_t extra, char* end) {\n  if (len == 0 || extra == 0) return;\n  if (start + len + extra >= end) return;\n  // move `len` characters to the right (in reverse since it can overlap)\n  for (size_t i = 1; i <= len; i++) {\n    start[len + extra - i] = start[len - i];\n  }\n  // and fill the start\n  for (size_t i = 0; i < extra; i++) {\n    start[i] = fill;\n  }\n}\n\n\nstatic void mi_out_num(uintmax_t x, size_t base, char prefix, char** out, char* end)\n{\n  if (x == 0 || base == 0 || base > 16) {\n    if (prefix != 0) { mi_outc(prefix, out, end); }\n    mi_outc('0',out,end);\n  }\n  else {\n    // output digits in reverse\n    char* start = *out;\n    while (x > 0) {\n      char digit = (char)(x % base);\n      mi_outc((digit <= 9 ? '0' + digit : 'A' + digit - 10),out,end);\n      x = x / base;\n    }\n    if (prefix != 0) {\n      mi_outc(prefix, out, end);\n    }\n    size_t len = *out - start;\n    // and reverse in-place\n    for (size_t i = 0; i < (len / 2); i++) {\n      char c = start[len - i - 1];\n      start[len - i - 1] = start[i];\n      start[i] = c;\n    }\n  }\n}\n\n\n#define MI_NEXTC()  c = *in; if (c==0) break; in++;\n\nint _mi_vsnprintf(char* buf, size_t bufsize, const char* fmt, va_list args) {\n  if (buf == NULL || bufsize == 0 || fmt == NULL) return 0;\n  buf[bufsize - 1] = 0;\n  char* const end = buf + (bufsize - 1);\n  const char* in = fmt;\n  char* out = buf;\n  while (true) {\n    if (out >= end) break;\n    char c;\n    MI_NEXTC();\n    if (c != '%') {\n      if ((c >= ' ' && c <= '~') || c=='\\n' || c=='\\r' || c=='\\t') { // output visible ascii or standard control only\n        mi_outc(c, &out, end);\n      }\n    }\n    else {\n      MI_NEXTC();\n      char   fill = ' ';\n      size_t width = 0;\n      char   numtype = 'd';\n      char   numplus = 0;\n      bool   alignright = true;\n      if (c == '+' || c == ' ') { numplus = c; MI_NEXTC(); }\n      if (c == '-') { alignright = false; MI_NEXTC(); }\n      if (c == '0') { fill = '0'; MI_NEXTC(); }\n      if (c >= '1' && c <= '9') {\n        width = (c - '0'); MI_NEXTC();\n        while (c >= '0' && c <= '9') {\n          width = (10 * width) + (c - '0'); MI_NEXTC();\n        }\n        if (c == 0) break;  // extra check due to while\n      }\n      if (c == 'z' || c == 't' || c == 'L') { numtype = c; MI_NEXTC(); }\n      else if (c == 'l') {\n        numtype = c; MI_NEXTC();\n        if (c == 'l') { numtype = 'L'; MI_NEXTC(); }\n      }\n\n      char* start = out;\n      if (c == 's') {\n        // string\n        const char* s = va_arg(args, const char*);\n        mi_outs(s, &out, end);\n      }\n      else if (c == 'p' || c == 'x' || c == 'u') {\n        // unsigned\n        uintmax_t x = 0;\n        if (c == 'x' || c == 'u') {\n          if (numtype == 'z')       x = va_arg(args, size_t);\n          else if (numtype == 't')  x = va_arg(args, uintptr_t); // unsigned ptrdiff_t\n          else if (numtype == 'L')  x = va_arg(args, unsigned long long);\n          else if (numtype == 'l')  x = va_arg(args, unsigned long);\n                               else x = va_arg(args, unsigned int);\n        }\n        else if (c == 'p') {\n          x = va_arg(args, uintptr_t);\n          mi_outs(\"0x\", &out, end);\n          start = out;\n          width = (width >= 2 ? width - 2 : 0);\n        }\n        if (width == 0 && (c == 'x' || c == 'p')) {\n          if (c == 'p')   { width = 2 * (x <= UINT32_MAX ? 4 : ((x >> 16) <= UINT32_MAX ? 6 : sizeof(void*))); }\n          if (width == 0) { width = 2; }\n          fill = '0';\n        }\n        mi_out_num(x, (c == 'x' || c == 'p' ? 16 : 10), numplus, &out, end);\n      }\n      else if (c == 'i' || c == 'd') {\n        // signed\n        intmax_t x = 0;\n        if (numtype == 'z')       x = va_arg(args, intptr_t );\n        else if (numtype == 't')  x = va_arg(args, ptrdiff_t);\n        else if (numtype == 'L')  x = va_arg(args, long long);\n        else if (numtype == 'l')  x = va_arg(args, long);\n                             else x = va_arg(args, int);\n        char pre = 0;\n        if (x < 0) {\n          pre = '-';\n          if (x > INTMAX_MIN) { x = -x; }\n        }\n        else if (numplus != 0) {\n          pre = numplus;\n        }\n        mi_out_num((uintmax_t)x, 10, pre, &out, end);\n      }\n      else if (c >= ' ' && c <= '~') {\n        // unknown format\n        mi_outc('%', &out, end);\n        mi_outc(c, &out, end);\n      }\n\n      // fill & align\n      mi_assert_internal(out <= end);\n      mi_assert_internal(out >= start);\n      const size_t len = out - start;\n      if (len < width) {\n        mi_out_fill(fill, width - len, &out, end);\n        if (alignright && out <= end) {\n          mi_out_alignright(fill, start, len, width - len, end);\n        }\n      }\n    }\n  }\n  mi_assert_internal(out <= end);\n  *out = 0;\n  return (int)(out - buf);\n}\n\nint _mi_snprintf(char* buf, size_t buflen, const char* fmt, ...) {\n  va_list args;\n  va_start(args, fmt);\n  const int written = _mi_vsnprintf(buf, buflen, fmt, args);\n  va_end(args);\n  return written;\n}\n\n\n#if MI_SIZE_SIZE == 4\n#define mi_mask_even_bits32      (0x55555555)\n#define mi_mask_even_pairs32     (0x33333333)\n#define mi_mask_even_nibbles32   (0x0F0F0F0F)\n\n// sum of all the bytes in `x` if it is guaranteed that the sum < 256!\nstatic size_t mi_byte_sum32(uint32_t x) {\n  // perform `x * 0x01010101`: the highest byte contains the sum of all bytes.\n  x += (x << 8);\n  x += (x << 16);\n  return (size_t)(x >> 24);\n}\n\nstatic size_t mi_popcount_generic32(uint32_t x) {\n  // first count each 2-bit group `a`, where: a==0b00 -> 00, a==0b01 -> 01, a==0b10 -> 01, a==0b11 -> 10\n  // in other words, `a - (a>>1)`; to do this in parallel, we need to mask to prevent spilling a bit pair\n  // into the lower bit-pair:\n  x = x - ((x >> 1) & mi_mask_even_bits32);\n  // add the 2-bit pair results\n  x = (x & mi_mask_even_pairs32) + ((x >> 2) & mi_mask_even_pairs32);\n  // add the 4-bit nibble results\n  x = (x + (x >> 4)) & mi_mask_even_nibbles32;\n  // each byte now has a count of its bits, we can sum them now:\n  return mi_byte_sum32(x);\n}\n\nmi_decl_noinline size_t _mi_popcount_generic(size_t x) {\n  return mi_popcount_generic32(x);\n}\n\n#else\n#define mi_mask_even_bits64      (0x5555555555555555)\n#define mi_mask_even_pairs64     (0x3333333333333333)\n#define mi_mask_even_nibbles64   (0x0F0F0F0F0F0F0F0F)\n\n// sum of all the bytes in `x` if it is guaranteed that the sum < 256!\nstatic size_t mi_byte_sum64(uint64_t x) {\n  x += (x << 8);\n  x += (x << 16);\n  x += (x << 32);\n  return (size_t)(x >> 56);\n}\n\nstatic size_t mi_popcount_generic64(uint64_t x) {\n  x = x - ((x >> 1) & mi_mask_even_bits64);\n  x = (x & mi_mask_even_pairs64) + ((x >> 2) & mi_mask_even_pairs64);\n  x = (x + (x >> 4)) & mi_mask_even_nibbles64;\n  return mi_byte_sum64(x);\n}\n\nmi_decl_noinline size_t _mi_popcount_generic(size_t x) {\n  return mi_popcount_generic64(x);\n}\n#endif\n\n"
  },
  {
    "path": "src/options.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"  // mi_prim_out_stderr\n\n#include <stdio.h>      // stdin/stdout\n#include <stdlib.h>     // abort\n\n\n\nstatic long mi_max_error_count   = 16; // stop outputting errors after this (use < 0 for no limit)\nstatic long mi_max_warning_count = 16; // stop outputting warnings after this (use < 0 for no limit)\n\nstatic void mi_add_stderr_output(void);\n\nint mi_version(void) mi_attr_noexcept {\n  return MI_MALLOC_VERSION;\n}\n\n\n// --------------------------------------------------------\n// Options\n// These can be accessed by multiple threads and may be\n// concurrently initialized, but an initializing data race\n// is ok since they resolve to the same value.\n// --------------------------------------------------------\ntypedef enum mi_init_e {\n  UNINIT,       // not yet initialized\n  DEFAULTED,    // not found in the environment, use default value\n  INITIALIZED   // found in environment or set explicitly\n} mi_init_t;\n\ntypedef struct mi_option_desc_s {\n  long        value;  // the value\n  mi_init_t   init;   // is it initialized yet? (from the environment)\n  mi_option_t option; // for debugging: the option index should match the option\n  const char* name;   // option name without `mimalloc_` prefix\n  const char* legacy_name; // potential legacy option name\n} mi_option_desc_t;\n\n#define MI_OPTION(opt)                  mi_option_##opt, #opt, NULL\n#define MI_OPTION_LEGACY(opt,legacy)    mi_option_##opt, #opt, #legacy\n\n// Some options can be set at build time for statically linked libraries\n// (use `-DMI_EXTRA_CPPDEFS=\"opt1=val1;opt2=val2\"`)\n//\n// This is useful if we cannot pass them as environment variables\n// (and setting them programmatically would be too late)\n\n#ifndef MI_DEFAULT_VERBOSE\n#define MI_DEFAULT_VERBOSE 0\n#endif\n\n#ifndef MI_DEFAULT_EAGER_COMMIT\n#define MI_DEFAULT_EAGER_COMMIT 1\n#endif\n\n#ifndef MI_DEFAULT_ARENA_EAGER_COMMIT\n#define MI_DEFAULT_ARENA_EAGER_COMMIT 2\n#endif\n\n// in KiB\n#ifndef MI_DEFAULT_ARENA_RESERVE\n #if (MI_INTPTR_SIZE>4)\n  #define MI_DEFAULT_ARENA_RESERVE 1024L*1024L\n #else\n  #define MI_DEFAULT_ARENA_RESERVE 128L*1024L\n #endif\n#endif\n\n#ifndef MI_DEFAULT_DISALLOW_ARENA_ALLOC\n#define MI_DEFAULT_DISALLOW_ARENA_ALLOC 0\n#endif\n\n#ifndef MI_DEFAULT_ALLOW_LARGE_OS_PAGES\n#define MI_DEFAULT_ALLOW_LARGE_OS_PAGES 0\n#endif\n\n#ifndef MI_DEFAULT_RESERVE_HUGE_OS_PAGES\n#define MI_DEFAULT_RESERVE_HUGE_OS_PAGES 0\n#endif\n\n#ifndef MI_DEFAULT_RESERVE_OS_MEMORY\n#define MI_DEFAULT_RESERVE_OS_MEMORY 0\n#endif\n\n#ifndef MI_DEFAULT_GUARDED_SAMPLE_RATE\n#if MI_GUARDED\n#define MI_DEFAULT_GUARDED_SAMPLE_RATE 4000\n#else\n#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0\n#endif\n#endif\n\n\n#ifndef MI_DEFAULT_ALLOW_THP\n#if defined(__ANDROID__)\n#define MI_DEFAULT_ALLOW_THP  0\n#else\n#define MI_DEFAULT_ALLOW_THP  1\n#endif\n#endif\n\n// Static options\nstatic mi_option_desc_t options[_mi_option_last] =\n{\n  // stable options\n  #if MI_DEBUG || defined(MI_SHOW_ERRORS)\n  { 1, UNINIT, MI_OPTION(show_errors) },\n  #else\n  { 0, UNINIT, MI_OPTION(show_errors) },\n  #endif\n  { 0, UNINIT, MI_OPTION(show_stats) },\n  { MI_DEFAULT_VERBOSE, UNINIT, MI_OPTION(verbose) },\n\n  // some of the following options are experimental and not all combinations are allowed.\n  { MI_DEFAULT_EAGER_COMMIT,\n       UNINIT, MI_OPTION(eager_commit) },               // commit per segment directly (4MiB)  (but see also `eager_commit_delay`)\n  { MI_DEFAULT_ARENA_EAGER_COMMIT,\n       UNINIT, MI_OPTION_LEGACY(arena_eager_commit,eager_region_commit) }, // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux)\n  { 1, UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) },        // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)\n  { MI_DEFAULT_ALLOW_LARGE_OS_PAGES,\n       UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) },    // use large OS pages, use only with eager commit to prevent fragmentation of VMA's\n  { MI_DEFAULT_RESERVE_HUGE_OS_PAGES,\n       UNINIT, MI_OPTION(reserve_huge_os_pages) },      // per 1GiB huge pages\n  {-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) },   // reserve huge pages at node N\n  { MI_DEFAULT_RESERVE_OS_MEMORY,\n       UNINIT, MI_OPTION(reserve_os_memory)     },      // reserve N KiB OS memory in advance (use `option_get_size`)\n  { 0, UNINIT, MI_OPTION(deprecated_segment_cache) },   // cache N segments per thread\n  { 0, UNINIT, MI_OPTION(deprecated_page_reset) },      // reset page memory on free\n  { 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_purge,abandoned_page_reset) },       // reset free page memory when a thread terminates\n  { 0, UNINIT, MI_OPTION(deprecated_segment_reset) },   // reset segment memory on free (needs eager commit)\n#if defined(__NetBSD__)\n  { 0, UNINIT, MI_OPTION(eager_commit_delay) },         // the first N segments per thread are not eagerly committed\n#else\n  { 1, UNINIT, MI_OPTION(eager_commit_delay) },         // the first N segments per thread are not eagerly committed (but per page in the segment on demand)\n#endif\n  { 10,  UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) },  // purge delay in milli-seconds\n  { 0,   UNINIT, MI_OPTION(use_numa_nodes) },           // 0 = use available numa nodes, otherwise use at most N nodes.\n  { 0,   UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) },           // 1 = do not use OS memory for allocation (but only reserved arenas)\n  { 100, UNINIT, MI_OPTION(os_tag) },                   // only apple specific for now but might serve more or less related purpose\n  { 32,  UNINIT, MI_OPTION(max_errors) },               // maximum errors that are output\n  { 32,  UNINIT, MI_OPTION(max_warnings) },             // maximum warnings that are output\n  { 10,  UNINIT, MI_OPTION(max_segment_reclaim)},       // max. percentage of the abandoned segments to be reclaimed per try.\n  { 0,   UNINIT, MI_OPTION(destroy_on_exit)},           // release all OS memory on process exit; careful with dangling pointer or after-exit frees!\n  { MI_DEFAULT_ARENA_RESERVE, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)\n  { 10,  UNINIT, MI_OPTION(arena_purge_mult) },         // purge delay multiplier for arena's\n  { 1,   UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },\n  { 0,   UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free\n  { MI_DEFAULT_DISALLOW_ARENA_ALLOC,   UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)\n  { 400, UNINIT, MI_OPTION(retry_on_oom) },             // windows only: retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries.\n#if defined(MI_VISIT_ABANDONED)\n  { 1,   INITIALIZED, MI_OPTION(visit_abandoned) },     // allow visiting heap blocks in abandoned segments; requires taking locks during reclaim.\n#else\n  { 0,   UNINIT, MI_OPTION(visit_abandoned) },\n#endif\n  { 0,   UNINIT, MI_OPTION(guarded_min) },              // only used when building with MI_GUARDED: minimal rounded object size for guarded objects\n  { MI_GiB, UNINIT, MI_OPTION(guarded_max) },           // only used when building with MI_GUARDED: maximal rounded object size for guarded objects\n  { 0,   UNINIT, MI_OPTION(guarded_precise) },          // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)\n  { MI_DEFAULT_GUARDED_SAMPLE_RATE,\n         UNINIT, MI_OPTION(guarded_sample_rate)},       // 1 out of N allocations in the min/max range will be guarded (=4000)\n  { 0,   UNINIT, MI_OPTION(guarded_sample_seed)},\n  { 0,   UNINIT, MI_OPTION(target_segments_per_thread) }, // abandon segments beyond this point, or 0 to disable.\n  { 10000, UNINIT, MI_OPTION(generic_collect) },          // collect heaps every N (=10000) generic allocation calls\n  { MI_DEFAULT_ALLOW_THP, \n         UNINIT, MI_OPTION(allow_thp) }                 // allow transparent huge pages?\n};\n\nstatic void mi_option_init(mi_option_desc_t* desc);\n\nstatic bool mi_option_has_size_in_kib(mi_option_t option) {\n  return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);\n}\n\nvoid _mi_options_init(void) {\n  // called on process load\n  mi_add_stderr_output(); // now it safe to use stderr for output\n  for(int i = 0; i < _mi_option_last; i++ ) {\n    mi_option_t option = (mi_option_t)i;\n    long l = mi_option_get(option); MI_UNUSED(l); // initialize\n  }\n  mi_max_error_count = mi_option_get(mi_option_max_errors);\n  mi_max_warning_count = mi_option_get(mi_option_max_warnings);\n  #if MI_GUARDED\n  if (mi_option_get(mi_option_guarded_sample_rate) > 0) {\n    if (mi_option_is_enabled(mi_option_allow_large_os_pages)) {\n      mi_option_disable(mi_option_allow_large_os_pages);\n      _mi_warning_message(\"option 'allow_large_os_pages' is disabled to allow for guarded objects\\n\");\n    }\n  }\n  #endif\n  if (mi_option_is_enabled(mi_option_verbose)) { mi_options_print(); }\n}\n\n#define mi_stringifyx(str)  #str                // and stringify\n#define mi_stringify(str)   mi_stringifyx(str)  // expand\n\nvoid mi_options_print(void) mi_attr_noexcept\n{\n  // show version\n  const int vermajor = MI_MALLOC_VERSION/100;\n  const int verminor = (MI_MALLOC_VERSION%100)/10;\n  const int verpatch = (MI_MALLOC_VERSION%10);\n  _mi_message(\"v%i.%i.%i%s%s (built on %s, %s)\\n\", vermajor, verminor, verpatch,\n      #if defined(MI_CMAKE_BUILD_TYPE)\n      \", \" mi_stringify(MI_CMAKE_BUILD_TYPE)\n      #else\n      \"\"\n      #endif\n      ,\n      #if defined(MI_GIT_DESCRIBE)\n      \", git \" mi_stringify(MI_GIT_DESCRIBE)\n      #else\n      \"\"\n      #endif\n      , __DATE__, __TIME__);\n\n  // show options\n  for (int i = 0; i < _mi_option_last; i++) {\n    mi_option_t option = (mi_option_t)i;\n    long l = mi_option_get(option); MI_UNUSED(l); // possibly initialize\n    mi_option_desc_t* desc = &options[option];\n    _mi_message(\"option '%s': %ld %s\\n\", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? \"KiB\" : \"\"));\n  }\n\n  // show build configuration\n  _mi_message(\"debug level : %d\\n\", MI_DEBUG );\n  _mi_message(\"secure level: %d\\n\", MI_SECURE );\n  _mi_message(\"mem tracking: %s\\n\", MI_TRACK_TOOL);\n  #if MI_GUARDED\n  _mi_message(\"guarded build: %s\\n\", mi_option_get(mi_option_guarded_sample_rate) != 0 ? \"enabled\" : \"disabled\");\n  #endif\n  #if MI_TSAN\n  _mi_message(\"thread santizer enabled\\n\");\n  #endif\n}\n\nlong _mi_option_get_fast(mi_option_t option) {\n  mi_assert(option >= 0 && option < _mi_option_last);\n  mi_option_desc_t* desc = &options[option];\n  mi_assert(desc->option == option);  // index should match the option\n  //mi_assert(desc->init != UNINIT);\n  return desc->value;\n}\n\n\nmi_decl_nodiscard long mi_option_get(mi_option_t option) {\n  mi_assert(option >= 0 && option < _mi_option_last);\n  if (option < 0 || option >= _mi_option_last) return 0;\n  mi_option_desc_t* desc = &options[option];\n  mi_assert(desc->option == option);  // index should match the option\n  if mi_unlikely(desc->init == UNINIT) {\n    mi_option_init(desc);\n  }\n  return desc->value;\n}\n\nmi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) {\n  long x = mi_option_get(option);\n  return (x < min ? min : (x > max ? max : x));\n}\n\nmi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {\n  const long x = mi_option_get(option);\n  size_t size = (x < 0 ? 0 : (size_t)x);\n  if (mi_option_has_size_in_kib(option)) {\n    size *= MI_KiB;\n  }\n  return size;\n}\n\nvoid mi_option_set(mi_option_t option, long value) {\n  mi_assert(option >= 0 && option < _mi_option_last);\n  if (option < 0 || option >= _mi_option_last) return;\n  mi_option_desc_t* desc = &options[option];\n  mi_assert(desc->option == option);  // index should match the option\n  desc->value = value;\n  desc->init = INITIALIZED;\n  // ensure min/max range; be careful to not recurse.\n  if (desc->option == mi_option_guarded_min && _mi_option_get_fast(mi_option_guarded_max) < value) {\n    mi_option_set(mi_option_guarded_max, value);\n  }\n  else if (desc->option == mi_option_guarded_max && _mi_option_get_fast(mi_option_guarded_min) > value) {\n    mi_option_set(mi_option_guarded_min, value);\n  }\n}\n\nvoid mi_option_set_default(mi_option_t option, long value) {\n  mi_assert(option >= 0 && option < _mi_option_last);\n  if (option < 0 || option >= _mi_option_last) return;\n  mi_option_desc_t* desc = &options[option];\n  if (desc->init != INITIALIZED) {\n    desc->value = value;\n  }\n}\n\nmi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) {\n  return (mi_option_get(option) != 0);\n}\n\nvoid mi_option_set_enabled(mi_option_t option, bool enable) {\n  mi_option_set(option, (enable ? 1 : 0));\n}\n\nvoid mi_option_set_enabled_default(mi_option_t option, bool enable) {\n  mi_option_set_default(option, (enable ? 1 : 0));\n}\n\nvoid mi_option_enable(mi_option_t option) {\n  mi_option_set_enabled(option,true);\n}\n\nvoid mi_option_disable(mi_option_t option) {\n  mi_option_set_enabled(option,false);\n}\n\nstatic void mi_cdecl mi_out_stderr(const char* msg, void* arg) {\n  MI_UNUSED(arg);\n  if (msg != NULL && msg[0] != 0) {\n    _mi_prim_out_stderr(msg);\n  }\n}\n\n// Since an output function can be registered earliest in the `main`\n// function we also buffer output that happens earlier. When\n// an output function is registered it is called immediately with\n// the output up to that point.\n#ifndef MI_MAX_DELAY_OUTPUT\n#define MI_MAX_DELAY_OUTPUT ((size_t)(16*1024))\n#endif\nstatic char out_buf[MI_MAX_DELAY_OUTPUT+1];\nstatic _Atomic(size_t) out_len;\n\nstatic void mi_cdecl mi_out_buf(const char* msg, void* arg) {\n  MI_UNUSED(arg);\n  if (msg==NULL) return;\n  if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return;\n  size_t n = _mi_strlen(msg);\n  if (n==0) return;\n  // claim space\n  size_t start = mi_atomic_add_acq_rel(&out_len, n);\n  if (start >= MI_MAX_DELAY_OUTPUT) return;\n  // check bound\n  if (start+n >= MI_MAX_DELAY_OUTPUT) {\n    n = MI_MAX_DELAY_OUTPUT-start-1;\n  }\n  _mi_memcpy(&out_buf[start], msg, n);\n}\n\nstatic void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {\n  if (out==NULL) return;\n  // claim (if `no_more_buf == true`, no more output will be added after this point)\n  size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));\n  // and output the current contents\n  if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;\n  out_buf[count] = 0;\n  out(out_buf,arg);\n  if (!no_more_buf) {\n    out_buf[count] = '\\n'; // if continue with the buffer, insert a newline\n  }\n}\n\n\n// Once this module is loaded, switch to this routine\n// which outputs to stderr and the delayed output buffer.\nstatic void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {\n  mi_out_stderr(msg,arg);\n  mi_out_buf(msg,arg);\n}\n\n\n\n// --------------------------------------------------------\n// Default output handler\n// --------------------------------------------------------\n\n// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.\n// For now, don't register output from multiple threads.\nstatic mi_output_fun* volatile mi_out_default; // = NULL\nstatic _Atomic(void*) mi_out_arg; // = NULL\n\nstatic mi_output_fun* mi_out_get_default(void** parg) {\n  if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }\n  mi_output_fun* out = mi_out_default;\n  return (out == NULL ? &mi_out_buf : out);\n}\n\nvoid mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {\n  mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer\n  mi_atomic_store_ptr_release(void,&mi_out_arg, arg);\n  if (out!=NULL) mi_out_buf_flush(out,true,arg);         // output all the delayed output now\n}\n\n// add stderr to the delayed output after the module is loaded\nstatic void mi_add_stderr_output(void) {\n  mi_assert_internal(mi_out_default == NULL);\n  mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr\n  mi_out_default = &mi_out_buf_stderr;           // and add stderr to the delayed output\n}\n\n// --------------------------------------------------------\n// Messages, all end up calling `_mi_fputs`.\n// --------------------------------------------------------\nstatic _Atomic(size_t) error_count;   // = 0;  // when >= max_error_count stop emitting errors\nstatic _Atomic(size_t) warning_count; // = 0;  // when >= max_warning_count stop emitting warnings\n\n// When overriding malloc, we may recurse into mi_vfprintf if an allocation\n// inside the C runtime causes another message.\n// In some cases (like on macOS) the loader already allocates which\n// calls into mimalloc; if we then access thread locals (like `recurse`)\n// this may crash as the access may call _tlv_bootstrap that tries to\n// (recursively) invoke malloc again to allocate space for the thread local\n// variables on demand. This is why we use a _mi_preloading test on such\n// platforms. However, C code generator may move the initial thread local address\n// load before the `if` and we therefore split it out in a separate function.\nstatic mi_decl_thread bool recurse = false;\n\nstatic mi_decl_noinline bool mi_recurse_enter_prim(void) {\n  if (recurse) return false;\n  recurse = true;\n  return true;\n}\n\nstatic mi_decl_noinline void mi_recurse_exit_prim(void) {\n  recurse = false;\n}\n\nstatic bool mi_recurse_enter(void) {\n  #if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)\n  if (_mi_preloading()) return false;\n  #endif\n  return mi_recurse_enter_prim();\n}\n\nstatic void mi_recurse_exit(void) {\n  #if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)\n  if (_mi_preloading()) return;\n  #endif\n  mi_recurse_exit_prim();\n}\n\nvoid _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {\n  if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr?\n    if (!mi_recurse_enter()) return;\n    out = mi_out_get_default(&arg);\n    if (prefix != NULL) out(prefix, arg);\n    out(message, arg);\n    mi_recurse_exit();\n  }\n  else {\n    if (prefix != NULL) out(prefix, arg);\n    out(message, arg);\n  }\n}\n\n// Define our own limited `fprintf` that avoids memory allocation.\n// We do this using `_mi_vsnprintf` with a limited buffer.\nstatic void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {\n  char buf[512];\n  if (fmt==NULL) return;\n  if (!mi_recurse_enter()) return;\n  _mi_vsnprintf(buf, sizeof(buf)-1, fmt, args);\n  mi_recurse_exit();\n  _mi_fputs(out,arg,prefix,buf);\n}\n\nvoid _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {\n  va_list args;\n  va_start(args,fmt);\n  mi_vfprintf(out,arg,NULL,fmt,args);\n  va_end(args);\n}\n\nstatic void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {\n  if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {\n    char tprefix[64];\n    _mi_snprintf(tprefix, sizeof(tprefix), \"%sthread 0x%tx: \", prefix, (uintptr_t)_mi_thread_id());\n    mi_vfprintf(out, arg, tprefix, fmt, args);\n  }\n  else {\n    mi_vfprintf(out, arg, prefix, fmt, args);\n  }\n}\n\nvoid _mi_message(const char* fmt, ...) {\n  va_list args;\n  va_start(args, fmt);\n  mi_vfprintf_thread(NULL, NULL, \"mimalloc: \", fmt, args);\n  va_end(args);\n}\n\nvoid _mi_trace_message(const char* fmt, ...) {\n  if (mi_option_get(mi_option_verbose) <= 1) return;  // only with verbose level 2 or higher\n  va_list args;\n  va_start(args, fmt);\n  mi_vfprintf_thread(NULL, NULL, \"mimalloc: \", fmt, args);\n  va_end(args);\n}\n\nvoid _mi_verbose_message(const char* fmt, ...) {\n  if (!mi_option_is_enabled(mi_option_verbose)) return;\n  va_list args;\n  va_start(args,fmt);\n  mi_vfprintf(NULL, NULL, \"mimalloc: \", fmt, args);\n  va_end(args);\n}\n\nstatic void mi_show_error_message(const char* fmt, va_list args) {\n  if (!mi_option_is_enabled(mi_option_verbose)) {\n    if (!mi_option_is_enabled(mi_option_show_errors)) return;\n    if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return;\n  }\n  mi_vfprintf_thread(NULL, NULL, \"mimalloc: error: \", fmt, args);\n}\n\nvoid _mi_warning_message(const char* fmt, ...) {\n  if (!mi_option_is_enabled(mi_option_verbose)) {\n    if (!mi_option_is_enabled(mi_option_show_errors)) return;\n    if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return;\n  }\n  va_list args;\n  va_start(args,fmt);\n  mi_vfprintf_thread(NULL, NULL, \"mimalloc: warning: \", fmt, args);\n  va_end(args);\n}\n\n\n#if MI_DEBUG\nmi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) mi_attr_noexcept {\n  _mi_fprintf(NULL, NULL, \"mimalloc: assertion failed: at \\\"%s\\\":%u, %s\\n  assertion: \\\"%s\\\"\\n\", fname, line, (func==NULL?\"\":func), assertion);\n  abort();\n}\n#endif\n\n// --------------------------------------------------------\n// Errors\n// --------------------------------------------------------\n\nstatic mi_error_fun* volatile  mi_error_handler; // = NULL\nstatic _Atomic(void*) mi_error_arg;     // = NULL\n\nstatic void mi_error_default(int err) {\n  MI_UNUSED(err);\n#if (MI_DEBUG>0)\n  if (err==EFAULT) {\n    #ifdef _MSC_VER\n    __debugbreak();\n    #endif\n    abort();\n  }\n#endif\n#if (MI_SECURE>0)\n  if (err==EFAULT) {  // abort on serious errors in secure mode (corrupted meta-data)\n    abort();\n  }\n#endif\n#if defined(MI_XMALLOC)\n  if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode\n    abort();\n  }\n#endif\n}\n\nvoid mi_register_error(mi_error_fun* fun, void* arg) {\n  mi_error_handler = fun;  // can be NULL\n  mi_atomic_store_ptr_release(void,&mi_error_arg, arg);\n}\n\nvoid _mi_error_message(int err, const char* fmt, ...) {\n  // show detailed error message\n  va_list args;\n  va_start(args, fmt);\n  mi_show_error_message(fmt, args);\n  va_end(args);\n  // and call the error handler which may abort (or return normally)\n  if (mi_error_handler != NULL) {\n    mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));\n  }\n  else {\n    mi_error_default(err);\n  }\n}\n\n// --------------------------------------------------------\n// Initialize options by checking the environment\n// --------------------------------------------------------\n\n// TODO: implement ourselves to reduce dependencies on the C runtime\n#include <stdlib.h> // strtol\n#include <string.h> // strstr\n\n\nstatic void mi_option_init(mi_option_desc_t* desc) {\n  // Read option value from the environment\n  char s[64 + 1];\n  char buf[64+1];\n  _mi_strlcpy(buf, \"mimalloc_\", sizeof(buf));\n  _mi_strlcat(buf, desc->name, sizeof(buf));\n  bool found = _mi_getenv(buf, s, sizeof(s));\n  if (!found && desc->legacy_name != NULL) {\n    _mi_strlcpy(buf, \"mimalloc_\", sizeof(buf));\n    _mi_strlcat(buf, desc->legacy_name, sizeof(buf));\n    found = _mi_getenv(buf, s, sizeof(s));\n    if (found) {\n      _mi_warning_message(\"environment option \\\"mimalloc_%s\\\" is deprecated -- use \\\"mimalloc_%s\\\" instead.\\n\", desc->legacy_name, desc->name);\n    }\n  }\n\n  if (found) {\n    size_t len = _mi_strnlen(s, sizeof(buf) - 1);\n    for (size_t i = 0; i < len; i++) {\n      buf[i] = _mi_toupper(s[i]);\n    }\n    buf[len] = 0;\n    if (buf[0] == 0 || strstr(\"1;TRUE;YES;ON\", buf) != NULL) {\n      desc->value = 1;\n      desc->init = INITIALIZED;\n    }\n    else if (strstr(\"0;FALSE;NO;OFF\", buf) != NULL) {\n      desc->value = 0;\n      desc->init = INITIALIZED;\n    }\n    else {\n      char* end = buf;\n      long value = strtol(buf, &end, 10);\n      if (mi_option_has_size_in_kib(desc->option)) {\n        // this option is interpreted in KiB to prevent overflow of `long` for large allocations\n        // (long is 32-bit on 64-bit windows, which allows for 4TiB max.)\n        size_t size = (value < 0 ? 0 : (size_t)value);\n        bool overflow = false;\n        if (*end == 'K') { end++; }\n        else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }\n        else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }\n        else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }\n        else { size = (size + MI_KiB - 1) / MI_KiB; }\n        if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB\n        else if (*end == 'B') { end++; }                  // Kb, Mb, Gb, Tb\n        if (overflow || size > MI_MAX_ALLOC_SIZE) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }\n        value = (size > LONG_MAX ? LONG_MAX : (long)size);\n      }\n      if (*end == 0) {\n        mi_option_set(desc->option, value);\n      }\n      else {\n        // set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.\n        desc->init = DEFAULTED;\n        if (desc->option == mi_option_verbose && desc->value == 0) {\n          // if the 'mimalloc_verbose' env var has a bogus value we'd never know\n          // (since the value defaults to 'off') so in that case briefly enable verbose\n          desc->value = 1;\n          _mi_warning_message(\"environment option mimalloc_%s has an invalid value.\\n\", desc->name);\n          desc->value = 0;\n        }\n        else {\n          _mi_warning_message(\"environment option mimalloc_%s has an invalid value.\\n\", desc->name);\n        }\n      }\n    }\n    mi_assert_internal(desc->init != UNINIT);\n  }\n  else if (!_mi_preloading()) {\n    desc->init = DEFAULTED;\n  }\n}\n"
  },
  {
    "path": "src/os.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2025, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"\n\n#define mi_os_stat_increase(stat,amount)      _mi_stat_increase(&_mi_stats_main.stat, amount)\n#define mi_os_stat_decrease(stat,amount)      _mi_stat_decrease(&_mi_stats_main.stat, amount)\n#define mi_os_stat_counter_increase(stat,inc) _mi_stat_counter_increase(&_mi_stats_main.stat, inc)\n\n/* -----------------------------------------------------------\n  Initialization.\n----------------------------------------------------------- */\n#ifndef MI_DEFAULT_VIRTUAL_ADDRESS_BITS\n#if MI_INTPTR_SIZE < 8\n#define MI_DEFAULT_VIRTUAL_ADDRESS_BITS     32\n#else\n#define MI_DEFAULT_VIRTUAL_ADDRESS_BITS     48\n#endif\n#endif\n\n#ifndef MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB\n#if MI_INTPTR_SIZE < 8\n#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB   4*MI_MiB    // 4 GiB\n#else\n#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB   32*MI_MiB   // 32 GiB\n#endif\n#endif\n\nstatic mi_os_mem_config_t mi_os_mem_config = {\n  4096,     // page size\n  0,        // large page size (usually 2MiB)\n  4096,     // allocation granularity\n  MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB,\n  MI_DEFAULT_VIRTUAL_ADDRESS_BITS,\n  true,     // has overcommit?  (if true we use MAP_NORESERVE on mmap systems)\n  false,    // can we partially free allocated blocks? (on mmap systems we can free anywhere in a mapped range, but on Windows we must free the entire span)\n  true      // has virtual reserve? (if true we can reserve virtual address space without using commit or physical memory)\n};\n\nbool _mi_os_has_overcommit(void) {\n  return mi_os_mem_config.has_overcommit;\n}\n\nbool _mi_os_has_virtual_reserve(void) {\n  return mi_os_mem_config.has_virtual_reserve;\n}\n\n\n// OS (small) page size\nsize_t _mi_os_page_size(void) {\n  return mi_os_mem_config.page_size;\n}\n\n// if large OS pages are supported (2 or 4MiB), then return the size, otherwise return the small page size (4KiB)\nsize_t _mi_os_large_page_size(void) {\n  return (mi_os_mem_config.large_page_size != 0 ? mi_os_mem_config.large_page_size : _mi_os_page_size());\n}\n\nbool _mi_os_canuse_large_page(size_t size, size_t alignment) {\n  // if we have access, check the size and alignment requirements\n  if (mi_os_mem_config.large_page_size == 0) return false;\n  return ((size % mi_os_mem_config.large_page_size) == 0 && (alignment % mi_os_mem_config.large_page_size) == 0);\n}\n\n// round to a good OS allocation size (bounded by max 12.5% waste)\nsize_t _mi_os_good_alloc_size(size_t size) {\n  size_t align_size;\n  if (size < 512*MI_KiB) align_size = _mi_os_page_size();\n  else if (size < 2*MI_MiB) align_size = 64*MI_KiB;\n  else if (size < 8*MI_MiB) align_size = 256*MI_KiB;\n  else if (size < 32*MI_MiB) align_size = 1*MI_MiB;\n  else align_size = 4*MI_MiB;\n  if mi_unlikely(size >= (SIZE_MAX - align_size)) return size; // possible overflow?\n  return _mi_align_up(size, align_size);\n}\n\nvoid _mi_os_init(void) {\n  _mi_prim_mem_init(&mi_os_mem_config);\n}\n\n\n/* -----------------------------------------------------------\n  Util\n-------------------------------------------------------------- */\nbool _mi_os_decommit(void* addr, size_t size);\nbool _mi_os_commit(void* addr, size_t size, bool* is_zero);\n\n\n/* -----------------------------------------------------------\n  aligned hinting\n-------------------------------------------------------------- */\n\n// On systems with enough virtual address bits, we can do efficient aligned allocation by using\n// the 2TiB to 30TiB area to allocate those. If we have at least 46 bits of virtual address\n// space (64TiB) we use this technique. (but see issue #939)\n#if (MI_INTPTR_SIZE >= 8) && !defined(MI_NO_ALIGNED_HINT)\nstatic mi_decl_cache_align _Atomic(uintptr_t)aligned_base;\n\n// Return a MI_SEGMENT_SIZE aligned address that is probably available.\n// If this returns NULL, the OS will determine the address but on some OS's that may not be\n// properly aligned which can be more costly as it needs to be adjusted afterwards.\n// For a size > 1GiB this always returns NULL in order to guarantee good ASLR randomization;\n// (otherwise an initial large allocation of say 2TiB has a 50% chance to include (known) addresses\n//  in the middle of the 2TiB - 6TiB address range (see issue #372))\n\n#define MI_HINT_BASE ((uintptr_t)2 << 40)  // 2TiB start\n#define MI_HINT_AREA ((uintptr_t)4 << 40)  // upto 6TiB   (since before win8 there is \"only\" 8TiB available to processes)\n#define MI_HINT_MAX  ((uintptr_t)30 << 40) // wrap after 30TiB (area after 32TiB is used for huge OS pages)\n\nvoid* _mi_os_get_aligned_hint(size_t try_alignment, size_t size)\n{\n  if (try_alignment <= 1 || try_alignment > MI_SEGMENT_SIZE) return NULL;\n  if (mi_os_mem_config.virtual_address_bits < 46) return NULL;  // < 64TiB virtual address space\n  size = _mi_align_up(size, MI_SEGMENT_SIZE);\n  if (size > 1*MI_GiB) return NULL;  // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<30) = 1/4096.\n  #if (MI_SECURE>0)\n  size += MI_SEGMENT_SIZE;        // put in `MI_SEGMENT_SIZE` virtual gaps between hinted blocks; this splits VLA's but increases guarded areas.\n  #endif\n\n  uintptr_t hint = mi_atomic_add_acq_rel(&aligned_base, size);\n  if (hint == 0 || hint > MI_HINT_MAX) {   // wrap or initialize\n    uintptr_t init = MI_HINT_BASE;\n    #if (MI_SECURE>0 || MI_DEBUG==0)       // security: randomize start of aligned allocations unless in debug mode\n    uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap());\n    init = init + ((MI_SEGMENT_SIZE * ((r>>17) & 0xFFFFF)) % MI_HINT_AREA);  // (randomly 20 bits)*4MiB == 0 to 4TiB\n    #endif\n    uintptr_t expected = hint + size;\n    mi_atomic_cas_strong_acq_rel(&aligned_base, &expected, init);\n    hint = mi_atomic_add_acq_rel(&aligned_base, size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all\n  }\n  if (hint%try_alignment != 0) return NULL;\n  return (void*)hint;\n}\n#else\nvoid* _mi_os_get_aligned_hint(size_t try_alignment, size_t size) {\n  MI_UNUSED(try_alignment); MI_UNUSED(size);\n  return NULL;\n}\n#endif\n\n/* -----------------------------------------------------------\n  Free memory\n-------------------------------------------------------------- */\n\nstatic void mi_os_free_huge_os_pages(void* p, size_t size);\n\nstatic void mi_os_prim_free(void* addr, size_t size, size_t commit_size) {\n  mi_assert_internal((size % _mi_os_page_size()) == 0);\n  if (addr == NULL) return; // || _mi_os_is_huge_reserved(addr)\n  int err = _mi_prim_free(addr, size);  // allow size==0 (issue #1041)\n  if (err != 0) {\n    _mi_warning_message(\"unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\\n\", err, err, size, addr);\n  }\n  if (commit_size > 0) {\n    mi_os_stat_decrease(committed, commit_size);\n  }\n  mi_os_stat_decrease(reserved, size);\n}\n\nvoid _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid) {\n  if (mi_memkind_is_os(memid.memkind)) {\n    size_t csize = memid.mem.os.size;\n    if (csize==0) { csize = _mi_os_good_alloc_size(size); }\n    mi_assert_internal(csize >= size);\n    size_t commit_size = (still_committed ? csize : 0);\n    void* base = addr;\n    // different base? (due to alignment)\n    if (memid.mem.os.base != base) {\n      mi_assert(memid.mem.os.base <= addr);\n      base = memid.mem.os.base;\n      const size_t diff = (uint8_t*)addr - (uint8_t*)memid.mem.os.base;\n      if (memid.mem.os.size==0) {\n        csize += diff;\n      }\n      if (still_committed) {\n        commit_size -= diff;  // the (addr-base) part was already un-committed\n      }\n    }\n    // free it\n    if (memid.memkind == MI_MEM_OS_HUGE) {\n      mi_assert(memid.is_pinned);\n      mi_os_free_huge_os_pages(base, csize);\n    }\n    else {\n      mi_os_prim_free(base, csize, (still_committed ? commit_size : 0));\n    }\n  }\n  else {\n    // nothing to do\n    mi_assert(memid.memkind < MI_MEM_OS);\n  }\n}\n\nvoid  _mi_os_free(void* p, size_t size, mi_memid_t memid) {\n  _mi_os_free_ex(p, size, true, memid);\n}\n\n\n/* -----------------------------------------------------------\n   Primitive allocation from the OS.\n-------------------------------------------------------------- */\n\n// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.\n// Also `hint_addr` is a hint and may be ignored.\nstatic void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {\n  mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);\n  mi_assert_internal(is_zero != NULL);\n  mi_assert_internal(is_large != NULL);\n  if (size == 0) return NULL;\n  if (!commit) { allow_large = false; }\n  if (try_alignment == 0) { try_alignment = 1; } // avoid 0 to ensure there will be no divide by zero when aligning\n  *is_zero = false;\n  void* p = NULL;\n  int err = _mi_prim_alloc(hint_addr, size, try_alignment, commit, allow_large, is_large, is_zero, &p);\n  if (err != 0) {\n    _mi_warning_message(\"unable to allocate OS memory (error: %d (0x%x), addr: %p, size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\\n\", err, err, hint_addr, size, try_alignment, commit, allow_large);\n  }\n\n\n\n  mi_os_stat_counter_increase(mmap_calls, 1);\n  if (p != NULL) {\n    mi_os_stat_increase(reserved, size);\n    if (commit) {\n      mi_os_stat_increase(committed, size);\n      // seems needed for asan (or `mimalloc-test-api` fails)\n      #ifdef MI_TRACK_ASAN\n      if (*is_zero) { mi_track_mem_defined(p,size); }\n               else { mi_track_mem_undefined(p,size); }\n      #endif\n    }\n  }\n  return p;\n}\n\nstatic void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {\n  return mi_os_prim_alloc_at(NULL, size, try_alignment, commit, allow_large, is_large, is_zero);\n}\n\n\n// Primitive aligned allocation from the OS.\n// This function guarantees the allocated memory is aligned.\nstatic void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** base) {\n  mi_assert_internal(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0));\n  mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);\n  mi_assert_internal(is_large != NULL);\n  mi_assert_internal(is_zero != NULL);\n  mi_assert_internal(base != NULL);\n  if (!commit) allow_large = false;\n  if (!(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0))) return NULL;\n  size = _mi_align_up(size, _mi_os_page_size());\n\n  // try first with a requested alignment hint (this will usually be aligned directly on Win 10+ or BSD)\n  void* p = mi_os_prim_alloc(size, alignment, commit, allow_large, is_large, is_zero);\n  if (p == NULL) return NULL;\n\n  // aligned already?\n  if (((uintptr_t)p % alignment) == 0) {\n    *base = p;\n  }\n  else {\n    // if not aligned, free it, overallocate, and unmap around it\n    #if !MI_TRACK_ASAN\n    _mi_warning_message(\"unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\\n\", size, p, alignment, commit);\n    #endif\n    if (p != NULL) { mi_os_prim_free(p, size, (commit ? size : 0)); }\n    if (size >= (SIZE_MAX - alignment)) return NULL; // overflow\n    const size_t over_size = size + alignment;\n\n    if (!mi_os_mem_config.has_partial_free) {  // win32 virtualAlloc cannot free parts of an allocated block\n      // over-allocate uncommitted (virtual) memory\n      p = mi_os_prim_alloc(over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, is_large, is_zero);\n      if (p == NULL) return NULL;\n\n      // set p to the aligned part in the full region\n      // note: this is dangerous on Windows as VirtualFree needs the actual base pointer\n      // this is handled though by having the `base` field in the memid's\n      *base = p; // remember the base\n      p = mi_align_up_ptr(p, alignment);\n\n      // explicitly commit only the aligned part\n      if (commit) {\n        if (!_mi_os_commit(p, size, NULL)) {\n          mi_os_prim_free(*base, over_size, 0);\n          return NULL;\n        }\n      }\n    }\n    else  { // mmap can free inside an allocation\n      // overallocate...\n      p = mi_os_prim_alloc(over_size, 1, commit, false, is_large, is_zero);\n      if (p == NULL) return NULL;\n\n      // and selectively unmap parts around the over-allocated area.\n      void* aligned_p = mi_align_up_ptr(p, alignment);\n      size_t pre_size = (uint8_t*)aligned_p - (uint8_t*)p;\n      size_t mid_size = _mi_align_up(size, _mi_os_page_size());\n      size_t post_size = over_size - pre_size - mid_size;\n      mi_assert_internal(pre_size < over_size&& post_size < over_size&& mid_size >= size);\n      if (pre_size > 0)  { mi_os_prim_free(p, pre_size, (commit ? pre_size : 0)); }\n      if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0)); }\n      // we can return the aligned pointer on `mmap` systems\n      p = aligned_p;\n      *base = aligned_p; // since we freed the pre part, `*base == p`.\n    }\n  }\n\n  mi_assert_internal(p == NULL || (p != NULL && *base != NULL && ((uintptr_t)p % alignment) == 0));\n  return p;\n}\n\n\n/* -----------------------------------------------------------\n  OS API: alloc and alloc_aligned\n----------------------------------------------------------- */\n\nvoid* _mi_os_alloc(size_t size, mi_memid_t* memid) {\n  *memid = _mi_memid_none();\n  if (size == 0) return NULL;\n  size = _mi_os_good_alloc_size(size);\n  bool os_is_large = false;\n  bool os_is_zero  = false;\n  void* p = mi_os_prim_alloc(size, 0, true, false, &os_is_large, &os_is_zero);\n  if (p == NULL) return NULL;\n\n  *memid = _mi_memid_create_os(p, size, true, os_is_zero, os_is_large);\n  mi_assert_internal(memid->mem.os.size >= size);\n  mi_assert_internal(memid->initially_committed);\n  return p;\n}\n\nvoid* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid)\n{\n  MI_UNUSED(&_mi_os_get_aligned_hint); // suppress unused warnings\n  *memid = _mi_memid_none();\n  if (size == 0) return NULL;\n  size = _mi_os_good_alloc_size(size);\n  alignment = _mi_align_up(alignment, _mi_os_page_size());\n\n  bool os_is_large = false;\n  bool os_is_zero  = false;\n  void* os_base = NULL;\n  void* p = mi_os_prim_alloc_aligned(size, alignment, commit, allow_large, &os_is_large, &os_is_zero, &os_base );\n  if (p == NULL) return NULL;\n\n  *memid = _mi_memid_create_os(p, size, commit, os_is_zero, os_is_large);\n  memid->mem.os.base = os_base;\n  memid->mem.os.size += ((uint8_t*)p - (uint8_t*)os_base);  // todo: return from prim_alloc_aligned?\n\n  mi_assert_internal(memid->mem.os.size >= size);\n  mi_assert_internal(_mi_is_aligned(p,alignment));\n  if (commit) { mi_assert_internal(memid->initially_committed); }\n  return p;\n}\n\n\nmi_decl_nodiscard static void* mi_os_ensure_zero(void* p, size_t size, mi_memid_t* memid) {\n  if (p==NULL || size==0) return p;\n  // ensure committed\n  if (!memid->initially_committed) {\n    bool is_zero = false;\n    if (!_mi_os_commit(p, size, &is_zero)) {\n      _mi_os_free(p, size, *memid);\n      return NULL;\n    }\n    memid->initially_committed = true;\n  }\n  // ensure zero'd\n  if (memid->initially_zero) return p;\n  _mi_memzero_aligned(p,size);\n  memid->initially_zero = true;\n  return p;\n}\n\nvoid*  _mi_os_zalloc(size_t size, mi_memid_t* memid) {\n  void* p = _mi_os_alloc(size,memid);\n  return mi_os_ensure_zero(p, size, memid);\n}\n\n/* -----------------------------------------------------------\n  OS aligned allocation with an offset. This is used\n  for large alignments > MI_BLOCK_ALIGNMENT_MAX. We use a large mimalloc\n  page where the object can be aligned at an offset from the start of the segment.\n  As we may need to overallocate, we need to free such pointers using `mi_free_aligned`\n  to use the actual start of the memory region.\n----------------------------------------------------------- */\n\nvoid* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid) {\n  mi_assert(offset <= MI_SEGMENT_SIZE);\n  mi_assert(offset <= size);\n  mi_assert((alignment % _mi_os_page_size()) == 0);\n  *memid = _mi_memid_none();\n  if (offset > MI_SEGMENT_SIZE) return NULL;\n  if (offset == 0) {\n    // regular aligned allocation\n    return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid);\n  }\n  else {\n    // overallocate to align at an offset\n    const size_t extra = _mi_align_up(offset, alignment) - offset;\n    const size_t oversize = size + extra;\n    void* const start = _mi_os_alloc_aligned(oversize, alignment, commit, allow_large, memid);\n    if (start == NULL) return NULL;\n\n    void* const p = (uint8_t*)start + extra;\n    mi_assert(_mi_is_aligned((uint8_t*)p + offset, alignment));\n    // decommit the overallocation at the start\n    if (commit && extra > _mi_os_page_size()) {\n      _mi_os_decommit(start, extra);\n    }\n    return p;\n  }\n}\n\n/* -----------------------------------------------------------\n  OS memory API: reset, commit, decommit, protect, unprotect.\n----------------------------------------------------------- */\n\n// OS page align within a given area, either conservative (pages inside the area only),\n// or not (straddling pages outside the area is possible)\nstatic void* mi_os_page_align_areax(bool conservative, void* addr, size_t size, size_t* newsize) {\n  mi_assert(addr != NULL && size > 0);\n  if (newsize != NULL) *newsize = 0;\n  if (size == 0 || addr == NULL) return NULL;\n\n  // page align conservatively within the range\n  void* start = (conservative ? mi_align_up_ptr(addr, _mi_os_page_size())\n    : mi_align_down_ptr(addr, _mi_os_page_size()));\n  void* end = (conservative ? mi_align_down_ptr((uint8_t*)addr + size, _mi_os_page_size())\n    : mi_align_up_ptr((uint8_t*)addr + size, _mi_os_page_size()));\n  ptrdiff_t diff = (uint8_t*)end - (uint8_t*)start;\n  if (diff <= 0) return NULL;\n\n  mi_assert_internal((conservative && (size_t)diff <= size) || (!conservative && (size_t)diff >= size));\n  if (newsize != NULL) *newsize = (size_t)diff;\n  return start;\n}\n\nstatic void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t* newsize) {\n  return mi_os_page_align_areax(true, addr, size, newsize);\n}\n\nbool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size) {\n  if (is_zero != NULL) { *is_zero = false; }\n  mi_os_stat_counter_increase(commit_calls, 1);\n\n  // page align range\n  size_t csize;\n  void* start = mi_os_page_align_areax(false /* conservative? */, addr, size, &csize);\n  if (csize == 0) return true;\n\n  // commit\n  bool os_is_zero = false;\n  int err = _mi_prim_commit(start, csize, &os_is_zero);\n  if (err != 0) {\n    _mi_warning_message(\"cannot commit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\\n\", err, err, start, csize);\n    return false;\n  }\n  if (os_is_zero && is_zero != NULL) {\n    *is_zero = true;\n    mi_assert_expensive(mi_mem_is_zero(start, csize));\n  }\n  // note: the following seems required for asan (otherwise `mimalloc-test-stress` fails)\n  #ifdef MI_TRACK_ASAN\n  if (os_is_zero) { mi_track_mem_defined(start,csize); }\n             else { mi_track_mem_undefined(start,csize); }\n  #endif\n  mi_os_stat_increase(committed, stat_size);  // use size for precise commit vs. decommit\n  return true;\n}\n\nbool _mi_os_commit(void* addr, size_t size, bool* is_zero) {\n  return _mi_os_commit_ex(addr, size, is_zero, size);\n}\n\nstatic bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, size_t stat_size) {\n  mi_assert_internal(needs_recommit!=NULL);\n  mi_os_stat_decrease(committed, stat_size);\n\n  // page align\n  size_t csize;\n  void* start = mi_os_page_align_area_conservative(addr, size, &csize);\n  if (csize == 0) return true;\n\n  // decommit\n  *needs_recommit = true;\n  int err = _mi_prim_decommit(start,csize,needs_recommit);\n  if (err != 0) {\n    _mi_warning_message(\"cannot decommit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\\n\", err, err, start, csize);\n  }\n  mi_assert_internal(err == 0);\n  return (err == 0);\n}\n\nbool _mi_os_decommit(void* addr, size_t size) {\n  bool needs_recommit;\n  return mi_os_decommit_ex(addr, size, &needs_recommit, size);\n}\n\n\n// Signal to the OS that the address range is no longer in use\n// but may be used later again. This will release physical memory\n// pages and reduce swapping while keeping the memory committed.\n// We page align to a conservative area inside the range to reset.\nbool _mi_os_reset(void* addr, size_t size) {\n  // page align conservatively within the range\n  size_t csize;\n  void* start = mi_os_page_align_area_conservative(addr, size, &csize);\n  if (csize == 0) return true;  // || _mi_os_is_huge_reserved(addr)\n  mi_os_stat_counter_increase(reset, csize);\n  mi_os_stat_counter_increase(reset_calls, 1);\n\n  #if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN\n  memset(start, 0, csize); // pretend it is eagerly reset\n  #endif\n\n  int err = _mi_prim_reset(start, csize);\n  if (err != 0) {\n    _mi_warning_message(\"cannot reset OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\\n\", err, err, start, csize);\n  }\n  return (err == 0);\n}\n\n\nvoid _mi_os_reuse( void* addr, size_t size ) {\n  // page align conservatively within the range\n  size_t csize = 0;\n  void* const start = mi_os_page_align_area_conservative(addr, size, &csize);\n  if (csize == 0) return;\n  const int err = _mi_prim_reuse(start, csize);\n  if (err != 0) {\n    _mi_warning_message(\"cannot reuse OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\\n\", err, err, start, csize);\n  }\n}\n\n// either resets or decommits memory, returns true if the memory needs\n// to be recommitted if it is to be re-used later on.\nbool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size)\n{\n  if (mi_option_get(mi_option_purge_delay) < 0) return false;  // is purging allowed?\n  mi_os_stat_counter_increase(purge_calls, 1);\n  mi_os_stat_counter_increase(purged, size);\n\n  if (mi_option_is_enabled(mi_option_purge_decommits) &&   // should decommit?\n      !_mi_preloading())                                   // don't decommit during preloading (unsafe)\n  {\n    bool needs_recommit = true;\n    mi_os_decommit_ex(p, size, &needs_recommit, stat_size);\n    return needs_recommit;\n  }\n  else {\n    if (allow_reset) {  // this can sometimes be not allowed if the range is not fully committed\n      _mi_os_reset(p, size);\n    }\n    return false;  // needs no recommit\n  }\n}\n\n// either resets or decommits memory, returns true if the memory needs\n// to be recommitted if it is to be re-used later on.\nbool _mi_os_purge(void* p, size_t size) {\n  return _mi_os_purge_ex(p, size, true, size);\n}\n\n// Protect a region in memory to be not accessible.\nstatic  bool mi_os_protectx(void* addr, size_t size, bool protect) {\n  // page align conservatively within the range\n  size_t csize = 0;\n  void* start = mi_os_page_align_area_conservative(addr, size, &csize);\n  if (csize == 0) return false;\n  /*\n  if (_mi_os_is_huge_reserved(addr)) {\n\t  _mi_warning_message(\"cannot mprotect memory allocated in huge OS pages\\n\");\n  }\n  */\n  int err = _mi_prim_protect(start,csize,protect);\n  if (err != 0) {\n    _mi_warning_message(\"cannot %s OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\\n\", (protect ? \"protect\" : \"unprotect\"), err, err, start, csize);\n  }\n  return (err == 0);\n}\n\nbool _mi_os_protect(void* addr, size_t size) {\n  return mi_os_protectx(addr, size, true);\n}\n\nbool _mi_os_unprotect(void* addr, size_t size) {\n  return mi_os_protectx(addr, size, false);\n}\n\n\n\n/* ----------------------------------------------------------------------------\nSupport for allocating huge OS pages (1Gib) that are reserved up-front\nand possibly associated with a specific NUMA node. (use `numa_node>=0`)\n-----------------------------------------------------------------------------*/\n#define MI_HUGE_OS_PAGE_SIZE  (MI_GiB)\n\n\n#if (MI_INTPTR_SIZE >= 8)\n// To ensure proper alignment, use our own area for huge OS pages\nstatic mi_decl_cache_align _Atomic(uintptr_t)  mi_huge_start; // = 0\n\n// Claim an aligned address range for huge pages\nstatic uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {\n  if (total_size != NULL) *total_size = 0;\n  const size_t size = pages * MI_HUGE_OS_PAGE_SIZE;\n\n  uintptr_t start = 0;\n  uintptr_t end = 0;\n  uintptr_t huge_start = mi_atomic_load_relaxed(&mi_huge_start);\n  do {\n    start = huge_start;\n    if (start == 0) {\n      // Initialize the start address after the 32TiB area\n      start = ((uintptr_t)32 << 40);  // 32TiB virtual start address\n    #if (MI_SECURE>0 || MI_DEBUG==0)      // security: randomize start of huge pages unless in debug mode\n      uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap());\n      start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF));  // (randomly 12bits)*1GiB == between 0 to 4TiB\n    #endif\n    }\n    end = start + size;\n    mi_assert_internal(end % MI_SEGMENT_SIZE == 0);\n  } while (!mi_atomic_cas_strong_acq_rel(&mi_huge_start, &huge_start, end));\n\n  if (total_size != NULL) *total_size = size;\n  return (uint8_t*)start;\n}\n#else\nstatic uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {\n  MI_UNUSED(pages);\n  if (total_size != NULL) *total_size = 0;\n  return NULL;\n}\n#endif\n\n// Allocate MI_SEGMENT_SIZE aligned huge pages\nvoid* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_msecs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid) {\n  *memid = _mi_memid_none();\n  if (psize != NULL) *psize = 0;\n  if (pages_reserved != NULL) *pages_reserved = 0;\n  size_t size = 0;\n  uint8_t* const start = mi_os_claim_huge_pages(pages, &size);\n  if (start == NULL) return NULL; // or 32-bit systems\n\n  // Allocate one page at the time but try to place them contiguously\n  // We allocate one page at the time to be able to abort if it takes too long\n  // or to at least allocate as many as available on the system.\n  mi_msecs_t start_t = _mi_clock_start();\n  size_t page = 0;\n  bool all_zero = true;\n  while (page < pages) {\n    // allocate a page\n    bool is_zero = false;\n    void* addr = start + (page * MI_HUGE_OS_PAGE_SIZE);\n    void* p = NULL;\n    int err = _mi_prim_alloc_huge_os_pages(addr, MI_HUGE_OS_PAGE_SIZE, numa_node, &is_zero, &p);\n    if (!is_zero) { all_zero = false;  }\n    if (err != 0) {\n      _mi_warning_message(\"unable to allocate huge OS page (error: %d (0x%x), address: %p, size: %zx bytes)\\n\", err, err, addr, MI_HUGE_OS_PAGE_SIZE);\n      break;\n    }\n\n    // Did we succeed at a contiguous address?\n    if (p != addr) {\n      // no success, issue a warning and break\n      if (p != NULL) {\n        _mi_warning_message(\"could not allocate contiguous huge OS page %zu at %p\\n\", page, addr);\n        mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);\n      }\n      break;\n    }\n\n    // success, record it\n    page++;  // increase before timeout check (see issue #711)\n    mi_os_stat_increase(committed, MI_HUGE_OS_PAGE_SIZE);\n    mi_os_stat_increase(reserved, MI_HUGE_OS_PAGE_SIZE);\n\n    // check for timeout\n    if (max_msecs > 0) {\n      mi_msecs_t elapsed = _mi_clock_end(start_t);\n      if (page >= 1) {\n        mi_msecs_t estimate = ((elapsed / (page+1)) * pages);\n        if (estimate > 2*max_msecs) { // seems like we are going to timeout, break\n          elapsed = max_msecs + 1;\n        }\n      }\n      if (elapsed > max_msecs) {\n        _mi_warning_message(\"huge OS page allocation timed out (after allocating %zu page(s))\\n\", page);\n        break;\n      }\n    }\n  }\n  mi_assert_internal(page*MI_HUGE_OS_PAGE_SIZE <= size);\n  if (pages_reserved != NULL) { *pages_reserved = page; }\n  if (psize != NULL) { *psize = page * MI_HUGE_OS_PAGE_SIZE; }\n  if (page != 0) {\n    mi_assert(start != NULL);\n    *memid = _mi_memid_create_os(start, size, true /* is committed */, all_zero, true /* is_large */);\n    memid->memkind = MI_MEM_OS_HUGE;\n    mi_assert(memid->is_pinned);\n    #ifdef MI_TRACK_ASAN\n    if (all_zero) { mi_track_mem_defined(start,size); }\n    #endif\n  }\n  return (page == 0 ? NULL : start);\n}\n\n// free every huge page in a range individually (as we allocated per page)\n// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems.\nstatic void mi_os_free_huge_os_pages(void* p, size_t size) {\n  if (p==NULL || size==0) return;\n  uint8_t* base = (uint8_t*)p;\n  while (size >= MI_HUGE_OS_PAGE_SIZE) {\n    mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);\n    size -= MI_HUGE_OS_PAGE_SIZE;\n    base += MI_HUGE_OS_PAGE_SIZE;\n  }\n}\n\n\n/* ----------------------------------------------------------------------------\nSupport NUMA aware allocation\n-----------------------------------------------------------------------------*/\n\nstatic _Atomic(size_t) mi_numa_node_count; // = 0   // cache the node count\n\nint _mi_os_numa_node_count(void) {\n  size_t count = mi_atomic_load_acquire(&mi_numa_node_count);\n  if mi_unlikely(count == 0) {\n    long ncount = mi_option_get(mi_option_use_numa_nodes); // given explicitly?\n    if (ncount > 0 && ncount < INT_MAX) {\n      count = (size_t)ncount;\n    }\n    else {\n      const size_t n = _mi_prim_numa_node_count(); // or detect dynamically\n      if (n == 0 || n > INT_MAX) { count = 1; }\n                            else { count = n; }\n    }\n    mi_atomic_store_release(&mi_numa_node_count, count); // save it\n    _mi_verbose_message(\"using %zd numa regions\\n\", count);\n  }\n  mi_assert_internal(count > 0 && count <= INT_MAX);\n  return (int)count;\n}\n\nstatic int mi_os_numa_node_get(void) {\n  int numa_count = _mi_os_numa_node_count();\n  if (numa_count<=1) return 0; // optimize on single numa node systems: always node 0\n  // never more than the node count and >= 0\n  const size_t n = _mi_prim_numa_node();\n  int numa_node = (n < INT_MAX ? (int)n : 0);\n  if (numa_node >= numa_count) { numa_node = numa_node % numa_count; }\n  return numa_node;\n}\n\nint _mi_os_numa_node(void) {\n  if mi_likely(mi_atomic_load_relaxed(&mi_numa_node_count) == 1) {\n    return 0;\n  }\n  else {\n    return mi_os_numa_node_get();\n  }\n}\n"
  },
  {
    "path": "src/page-queue.c",
    "content": "/*----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* -----------------------------------------------------------\n  Definition of page queues for each block size\n----------------------------------------------------------- */\n\n#ifndef MI_IN_PAGE_C\n#error \"this file should be included from 'page.c'\"\n// include to help an IDE\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#endif\n\n/* -----------------------------------------------------------\n  Minimal alignment in machine words (i.e. `sizeof(void*)`)\n----------------------------------------------------------- */\n\n#if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE)\n  #error \"define alignment for more than 4x word size for this platform\"\n#elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE)\n  #define MI_ALIGN4W   // 4 machine words minimal alignment\n#elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE)\n  #define MI_ALIGN2W   // 2 machine words minimal alignment\n#else\n  // ok, default alignment is 1 word\n#endif\n\n\n/* -----------------------------------------------------------\n  Queue query\n----------------------------------------------------------- */\n\n\nstatic inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) {\n  return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+sizeof(uintptr_t)));\n}\n\nstatic inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) {\n  return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+(2*sizeof(uintptr_t))));\n}\n\nstatic inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) {\n  return (pq->block_size > MI_MEDIUM_OBJ_SIZE_MAX);\n}\n\n/* -----------------------------------------------------------\n  Bins\n----------------------------------------------------------- */\n\n// Return the bin for a given field size.\n// Returns MI_BIN_HUGE if the size is too large.\n// We use `wsize` for the size in \"machine word sizes\",\n// i.e. byte size == `wsize*sizeof(void*)`.\nstatic inline size_t mi_bin(size_t size) {\n  size_t wsize = _mi_wsize_from_size(size);\n#if defined(MI_ALIGN4W)\n  if mi_likely(wsize <= 4) {\n    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes\n  }\n#elif defined(MI_ALIGN2W)\n  if mi_likely(wsize <= 8) {\n    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes\n  }\n#else\n  if mi_likely(wsize <= 8) {\n    return (wsize == 0 ? 1 : wsize);\n  }\n#endif\n  else if mi_unlikely(wsize > MI_MEDIUM_OBJ_WSIZE_MAX) {\n    return MI_BIN_HUGE;\n  }\n  else {\n    #if defined(MI_ALIGN4W)\n    if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes\n    #endif\n    wsize--;\n    // find the highest bit\n    const size_t b = (MI_SIZE_BITS - 1 - mi_clz(wsize));  // note: wsize != 0\n    // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation).\n    // - adjust with 3 because we use do not round the first 8 sizes\n    //   which each get an exact bin\n    const size_t bin = ((b << 2) + ((wsize >> (b - 2)) & 0x03)) - 3;\n    mi_assert_internal(bin > 0 && bin < MI_BIN_HUGE);\n    return bin;\n  }\n}\n\n\n\n/* -----------------------------------------------------------\n  Queue of pages with free blocks\n----------------------------------------------------------- */\n\nsize_t _mi_bin(size_t size) {\n  return mi_bin(size);\n}\n\nsize_t _mi_bin_size(size_t bin) {\n  return _mi_heap_empty.pages[bin].block_size;\n}\n\n// Good size for allocation\nsize_t mi_good_size(size_t size) mi_attr_noexcept {\n  if (size <= MI_MEDIUM_OBJ_SIZE_MAX) {\n    return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE));\n  }\n  else {\n    return _mi_align_up(size + MI_PADDING_SIZE,_mi_os_page_size());\n  }\n}\n\n#if (MI_DEBUG>1)\nstatic bool mi_page_queue_contains(mi_page_queue_t* queue, const mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  mi_page_t* list = queue->first;\n  while (list != NULL) {\n    mi_assert_internal(list->next == NULL || list->next->prev == list);\n    mi_assert_internal(list->prev == NULL || list->prev->next == list);\n    if (list == page) break;\n    list = list->next;\n  }\n  return (list == page);\n}\n\n#endif\n\n#if (MI_DEBUG>1)\nstatic bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t* pq) {\n  return (pq >= &heap->pages[0] && pq <= &heap->pages[MI_BIN_FULL]);\n}\n#endif\n\nstatic inline bool mi_page_is_large_or_huge(const mi_page_t* page) {\n  return (mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_huge(page));\n}\n\nstatic size_t mi_page_bin(const mi_page_t* page) {\n  const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));\n  mi_assert_internal(bin <= MI_BIN_FULL);\n  return bin;\n}\n\n// returns the page bin without using MI_BIN_FULL for statistics\nsize_t _mi_page_stats_bin(const mi_page_t* page) {\n  const size_t bin = (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page)));\n  mi_assert_internal(bin <= MI_BIN_HUGE);\n  return bin;\n}\n\nstatic mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) {\n  mi_assert_internal(heap!=NULL);\n  const size_t bin = mi_page_bin(page);\n  mi_page_queue_t* pq = &heap->pages[bin];\n  mi_assert_internal((mi_page_block_size(page) == pq->block_size) ||\n                       (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(pq)) ||\n                         (mi_page_is_in_full(page) && mi_page_queue_is_full(pq)));\n  return pq;\n}\n\nstatic mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) {\n  mi_heap_t* heap = mi_page_heap(page);\n  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);\n  mi_assert_expensive(mi_page_queue_contains(pq, page));\n  return pq;\n}\n\n// The current small page array is for efficiency and for each\n// small size (up to 256) it points directly to the page for that\n// size without having to compute the bin. This means when the\n// current free page queue is updated for a small bin, we need to update a\n// range of entries in `_mi_page_small_free`.\nstatic inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) {\n  mi_assert_internal(mi_heap_contains_queue(heap,pq));\n  size_t size = pq->block_size;\n  if (size > MI_SMALL_SIZE_MAX) return;\n\n  mi_page_t* page = pq->first;\n  if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty;\n\n  // find index in the right direct page array\n  size_t start;\n  size_t idx = _mi_wsize_from_size(size);\n  mi_page_t** pages_free = heap->pages_free_direct;\n\n  if (pages_free[idx] == page) return;  // already set\n\n  // find start slot\n  if (idx<=1) {\n    start = 0;\n  }\n  else {\n    // find previous size; due to minimal alignment upto 3 previous bins may need to be skipped\n    size_t bin = mi_bin(size);\n    const mi_page_queue_t* prev = pq - 1;\n    while( bin == mi_bin(prev->block_size) && prev > &heap->pages[0]) {\n      prev--;\n    }\n    start = 1 + _mi_wsize_from_size(prev->block_size);\n    if (start > idx) start = idx;\n  }\n\n  // set size range to the right page\n  mi_assert(start <= idx);\n  for (size_t sz = start; sz <= idx; sz++) {\n    pages_free[sz] = page;\n  }\n}\n\n/*\nstatic bool mi_page_queue_is_empty(mi_page_queue_t* queue) {\n  return (queue->first == NULL);\n}\n*/\n\nstatic void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(mi_page_queue_contains(queue, page));\n  mi_assert_internal(mi_page_block_size(page) == queue->block_size ||\n                      (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||\n                        (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));\n  mi_heap_t* heap = mi_page_heap(page);\n\n  if (page->prev != NULL) page->prev->next = page->next;\n  if (page->next != NULL) page->next->prev = page->prev;\n  if (page == queue->last)  queue->last = page->prev;\n  if (page == queue->first) {\n    queue->first = page->next;\n    // update first\n    mi_assert_internal(mi_heap_contains_queue(heap, queue));\n    mi_heap_queue_first_update(heap,queue);\n  }\n  heap->page_count--;\n  page->next = NULL;\n  page->prev = NULL;\n  // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), NULL);\n  mi_page_set_in_full(page,false);\n}\n\n\nstatic void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {\n  mi_assert_internal(mi_page_heap(page) == heap);\n  mi_assert_internal(!mi_page_queue_contains(queue, page));\n  #if MI_HUGE_PAGE_ABANDON\n  mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);\n  #endif\n  mi_assert_internal(mi_page_block_size(page) == queue->block_size ||\n                      (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||\n                        (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));\n\n  mi_page_set_in_full(page, mi_page_queue_is_full(queue));\n  // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), heap);\n  page->next = queue->first;\n  page->prev = NULL;\n  if (queue->first != NULL) {\n    mi_assert_internal(queue->first->prev == NULL);\n    queue->first->prev = page;\n    queue->first = page;\n  }\n  else {\n    queue->first = queue->last = page;\n  }\n\n  // update direct\n  mi_heap_queue_first_update(heap, queue);\n  heap->page_count++;\n}\n\nstatic void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {\n  mi_assert_internal(mi_page_heap(page) == heap);\n  mi_assert_internal(mi_page_queue_contains(queue, page));\n  if (queue->first == page) return;\n  mi_page_queue_remove(queue, page);\n  mi_page_queue_push(heap, queue, page);\n  mi_assert_internal(queue->first == page);\n}\n\nstatic void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(mi_page_queue_contains(from, page));\n  mi_assert_expensive(!mi_page_queue_contains(to, page));\n  const size_t bsize = mi_page_block_size(page);\n  MI_UNUSED(bsize);\n  mi_assert_internal((bsize == to->block_size && bsize == from->block_size) ||\n                     (bsize == to->block_size && mi_page_queue_is_full(from)) ||\n                     (bsize == from->block_size && mi_page_queue_is_full(to)) ||\n                     (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(to)) ||\n                     (mi_page_is_large_or_huge(page) && mi_page_queue_is_full(to)));\n\n  mi_heap_t* heap = mi_page_heap(page);\n\n  // delete from `from`\n  if (page->prev != NULL) page->prev->next = page->next;\n  if (page->next != NULL) page->next->prev = page->prev;\n  if (page == from->last)  from->last = page->prev;\n  if (page == from->first) {\n    from->first = page->next;\n    // update first\n    mi_assert_internal(mi_heap_contains_queue(heap, from));\n    mi_heap_queue_first_update(heap, from);\n  }\n\n  // insert into `to`\n  if (enqueue_at_end) {\n    // enqueue at the end\n    page->prev = to->last;\n    page->next = NULL;\n    if (to->last != NULL) {\n      mi_assert_internal(heap == mi_page_heap(to->last));\n      to->last->next = page;\n      to->last = page;\n    }\n    else {\n      to->first = page;\n      to->last = page;\n      mi_heap_queue_first_update(heap, to);\n    }\n  }\n  else {\n    if (to->first != NULL) {\n      // enqueue at 2nd place\n      mi_assert_internal(heap == mi_page_heap(to->first));\n      mi_page_t* next = to->first->next;\n      page->prev = to->first;\n      page->next = next;\n      to->first->next = page;\n      if (next != NULL) {\n        next->prev = page;\n      }\n      else {\n        to->last = page;\n      }\n    }\n    else {\n      // enqueue at the head (singleton list)\n      page->prev = NULL;\n      page->next = NULL;\n      to->first = page;\n      to->last = page;\n      mi_heap_queue_first_update(heap, to);\n    }\n  }\n\n  mi_page_set_in_full(page, mi_page_queue_is_full(to));\n}\n\nstatic void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {\n  mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end */, page);\n}\n\nstatic void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {\n  // note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`)\n  mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end of the `to` queue? */, page);\n}\n\n// Only called from `mi_heap_absorb`.\nsize_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) {\n  mi_assert_internal(mi_heap_contains_queue(heap,pq));\n  mi_assert_internal(pq->block_size == append->block_size);\n\n  if (append->first==NULL) return 0;\n\n  // set append pages to new heap and count\n  size_t count = 0;\n  for (mi_page_t* page = append->first; page != NULL; page = page->next) {\n    // inline `mi_page_set_heap` to avoid wrong assertion during absorption;\n    // in this case it is ok to be delayed freeing since both \"to\" and \"from\" heap are still alive.\n    mi_atomic_store_release(&page->xheap, (uintptr_t)heap);\n    // set the flag to delayed free (not overriding NEVER_DELAYED_FREE) which has as a\n    // side effect that it spins until any DELAYED_FREEING is finished. This ensures\n    // that after appending only the new heap will be used for delayed free operations.\n    _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false);\n    count++;\n  }\n\n  if (pq->last==NULL) {\n    // take over afresh\n    mi_assert_internal(pq->first==NULL);\n    pq->first = append->first;\n    pq->last = append->last;\n    mi_heap_queue_first_update(heap, pq);\n  }\n  else {\n    // append to end\n    mi_assert_internal(pq->last!=NULL);\n    mi_assert_internal(append->first!=NULL);\n    pq->last->next = append->first;\n    append->first->prev = pq->last;\n    pq->last = append->last;\n  }\n  return count;\n}\n"
  },
  {
    "path": "src/page.c",
    "content": "/*----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* -----------------------------------------------------------\n  The core of the allocator. Every segment contains\n  pages of a certain block size. The main function\n  exported is `mi_malloc_generic`.\n----------------------------------------------------------- */\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n\n/* -----------------------------------------------------------\n  Definition of page queues for each block size\n----------------------------------------------------------- */\n\n#define MI_IN_PAGE_C\n#include \"page-queue.c\"\n#undef MI_IN_PAGE_C\n\n\n/* -----------------------------------------------------------\n  Page helpers\n----------------------------------------------------------- */\n\n// Index a block in a page\nstatic inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) {\n  MI_UNUSED(page);\n  mi_assert_internal(page != NULL);\n  mi_assert_internal(i <= page->reserved);\n  return (mi_block_t*)((uint8_t*)page_start + (i * block_size));\n}\n\nstatic void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld);\nstatic bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld);\n\n#if (MI_DEBUG>=3)\nstatic size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) {\n  size_t count = 0;\n  while (head != NULL) {\n    mi_assert_internal(page == _mi_ptr_page(head));\n    count++;\n    head = mi_block_next(page, head);\n  }\n  return count;\n}\n\n/*\n// Start of the page available memory\nstatic inline uint8_t* mi_page_area(const mi_page_t* page) {\n  return _mi_page_start(_mi_page_segment(page), page, NULL);\n}\n*/\n\nstatic bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {\n  size_t psize;\n  uint8_t* page_area = _mi_segment_page_start(_mi_page_segment(page), page, &psize);\n  mi_block_t* start = (mi_block_t*)page_area;\n  mi_block_t* end   = (mi_block_t*)(page_area + psize);\n  while(p != NULL) {\n    if (p < start || p >= end) return false;\n    p = mi_block_next(page, p);\n  }\n#if MI_DEBUG>3 // generally too expensive to check this\n  if (page->free_is_zero) {\n    const size_t ubsize = mi_page_usable_block_size(page);\n    for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {\n      mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));\n    }\n  }\n#endif\n  return true;\n}\n\nstatic bool mi_page_is_valid_init(mi_page_t* page) {\n  mi_assert_internal(mi_page_block_size(page) > 0);\n  mi_assert_internal(page->used <= page->capacity);\n  mi_assert_internal(page->capacity <= page->reserved);\n\n  uint8_t* start = mi_page_start(page);\n  mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL));\n  mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE));\n  //mi_assert_internal(start + page->capacity*page->block_size == page->top);\n\n  mi_assert_internal(mi_page_list_is_valid(page,page->free));\n  mi_assert_internal(mi_page_list_is_valid(page,page->local_free));\n\n  #if MI_DEBUG>3 // generally too expensive to check this\n  if (page->free_is_zero) {\n    const size_t ubsize = mi_page_usable_block_size(page);\n    for(mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) {\n      mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));\n    }\n  }\n  #endif\n\n  #if !MI_TRACK_ENABLED && !MI_TSAN\n  mi_block_t* tfree = mi_page_thread_free(page);\n  mi_assert_internal(mi_page_list_is_valid(page, tfree));\n  //size_t tfree_count = mi_page_list_count(page, tfree);\n  //mi_assert_internal(tfree_count <= page->thread_freed + 1);\n  #endif\n\n  size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free);\n  mi_assert_internal(page->used + free_count == page->capacity);\n\n  return true;\n}\n\nextern mi_decl_hidden bool _mi_process_is_initialized;             // has mi_process_init been called?\n\nbool _mi_page_is_valid(mi_page_t* page) {\n  mi_assert_internal(mi_page_is_valid_init(page));\n  #if MI_SECURE\n  mi_assert_internal(page->keys[0] != 0);\n  #endif\n  if (mi_page_heap(page)!=NULL) {\n    mi_segment_t* segment = _mi_page_segment(page);\n\n    mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);\n    #if MI_HUGE_PAGE_ABANDON\n    if (segment->kind != MI_SEGMENT_HUGE)\n    #endif\n    {\n      mi_page_queue_t* pq = mi_page_queue_of(page);\n      mi_assert_internal(mi_page_queue_contains(pq, page));\n      mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page));\n      mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq));\n    }\n  }\n  return true;\n}\n#endif\n\nvoid _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {\n  while (!_mi_page_try_use_delayed_free(page, delay, override_never)) {\n    mi_atomic_yield();\n  }\n}\n\nbool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {\n  mi_thread_free_t tfreex;\n  mi_delayed_t     old_delay;\n  mi_thread_free_t tfree;\n  size_t yield_count = 0;\n  do {\n    tfree = mi_atomic_load_acquire(&page->xthread_free); // note: must acquire as we can break/repeat this loop and not do a CAS;\n    tfreex = mi_tf_set_delayed(tfree, delay);\n    old_delay = mi_tf_delayed(tfree);\n    if mi_unlikely(old_delay == MI_DELAYED_FREEING) {\n      if (yield_count >= 4) return false;  // give up after 4 tries\n      yield_count++;\n      mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done.\n      // tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail\n    }\n    else if (delay == old_delay) {\n      break; // avoid atomic operation if already equal\n    }\n    else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) {\n      break; // leave never-delayed flag set\n    }\n  } while ((old_delay == MI_DELAYED_FREEING) ||\n           !mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));\n\n  return true; // success\n}\n\n/* -----------------------------------------------------------\n  Page collect the `local_free` and `thread_free` lists\n----------------------------------------------------------- */\n\n// Collect the local `thread_free` list using an atomic exchange.\n// Note: The exchange must be done atomically as this is used right after\n// moving to the full list in `mi_page_collect_ex` and we need to\n// ensure that there was no race where the page became unfull just before the move.\nstatic void _mi_page_thread_free_collect(mi_page_t* page)\n{\n  mi_block_t* head;\n  mi_thread_free_t tfreex;\n  mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free);\n  do {\n    head = mi_tf_block(tfree);\n    tfreex = mi_tf_set_block(tfree,NULL);\n  } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex));\n\n  // return if the list is empty\n  if (head == NULL) return;\n\n  // find the tail -- also to get a proper count (without data races)\n  size_t max_count = page->capacity; // cannot collect more than capacity\n  size_t count = 1;\n  mi_block_t* tail = head;\n  mi_block_t* next;\n  while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) {\n    count++;\n    tail = next;\n  }\n  // if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free)\n  if (count > max_count) {\n    _mi_error_message(EFAULT, \"corrupted thread-free list\\n\");\n    return; // the thread-free items cannot be freed\n  }\n\n  // and append the current local free list\n  mi_block_set_next(page,tail, page->local_free);\n  page->local_free = head;\n\n  // update counts now\n  page->used -= (uint16_t)count;\n}\n\nvoid _mi_page_free_collect(mi_page_t* page, bool force) {\n  mi_assert_internal(page!=NULL);\n\n  // collect the thread free list\n  if (force || mi_page_thread_free(page) != NULL) {  // quick test to avoid an atomic operation\n    _mi_page_thread_free_collect(page);\n  }\n\n  // and the local free list\n  if (page->local_free != NULL) {\n    if mi_likely(page->free == NULL) {\n      // usual case\n      page->free = page->local_free;\n      page->local_free = NULL;\n      page->free_is_zero = false;\n    }\n    else if (force) {\n      // append -- only on shutdown (force) as this is a linear operation\n      mi_block_t* tail = page->local_free;\n      mi_block_t* next;\n      while ((next = mi_block_next(page, tail)) != NULL) {\n        tail = next;\n      }\n      mi_block_set_next(page, tail, page->free);\n      page->free = page->local_free;\n      page->local_free = NULL;\n      page->free_is_zero = false;\n    }\n  }\n\n  mi_assert_internal(!force || page->local_free == NULL);\n}\n\n\n\n/* -----------------------------------------------------------\n  Page fresh and retire\n----------------------------------------------------------- */\n\n// called from segments when reclaiming abandoned pages\nvoid _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {\n  mi_assert_expensive(mi_page_is_valid_init(page));\n\n  mi_assert_internal(mi_page_heap(page) == heap);\n  mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE);\n  #if MI_HUGE_PAGE_ABANDON\n  mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);\n  #endif\n\n  // TODO: push on full queue immediately if it is full?\n  mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page));\n  mi_page_queue_push(heap, pq, page);\n  mi_assert_expensive(_mi_page_is_valid(page));\n}\n\n// allocate a fresh page from a segment\nstatic mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size, size_t page_alignment) {\n  #if !MI_HUGE_PAGE_ABANDON\n  mi_assert_internal(pq != NULL);\n  mi_assert_internal(mi_heap_contains_queue(heap, pq));\n  mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size);\n  #endif\n  mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments);\n  if (page == NULL) {\n    // this may be out-of-memory, or an abandoned page was reclaimed (and in our queue)\n    return NULL;\n  }\n  #if MI_HUGE_PAGE_ABANDON\n  mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE);\n  #endif\n  mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE);\n  mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size);\n  // a fresh page was found, initialize it\n  const size_t full_block_size = (pq == NULL || mi_page_is_huge(page) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc\n  mi_assert_internal(full_block_size >= block_size);\n  mi_page_init(heap, page, full_block_size, heap->tld);\n  mi_heap_stat_increase(heap, pages, 1);\n  mi_heap_stat_increase(heap, page_bins[_mi_page_stats_bin(page)], 1);\n  if (pq != NULL) { mi_page_queue_push(heap, pq, page); }\n  mi_assert_expensive(_mi_page_is_valid(page));\n  return page;\n}\n\n// Get a fresh page to use\nstatic mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) {\n  mi_assert_internal(mi_heap_contains_queue(heap, pq));\n  mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size, 0);\n  if (page==NULL) return NULL;\n  mi_assert_internal(pq->block_size==mi_page_block_size(page));\n  mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page)));\n  return page;\n}\n\n/* -----------------------------------------------------------\n   Do any delayed frees\n   (put there by other threads if they deallocated in a full page)\n----------------------------------------------------------- */\nvoid _mi_heap_delayed_free_all(mi_heap_t* heap) {\n  while (!_mi_heap_delayed_free_partial(heap)) {\n    mi_atomic_yield();\n  }\n}\n\n// returns true if all delayed frees were processed\nbool _mi_heap_delayed_free_partial(mi_heap_t* heap) {\n  // take over the list (note: no atomic exchange since it is often NULL)\n  mi_block_t* block = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);\n  while (block != NULL && !mi_atomic_cas_ptr_weak_acq_rel(mi_block_t, &heap->thread_delayed_free, &block, NULL)) { /* nothing */ };\n  bool all_freed = true;\n\n  // and free them all\n  while(block != NULL) {\n    mi_block_t* next = mi_block_nextx(heap,block, heap->keys);\n    // use internal free instead of regular one to keep stats etc correct\n    if (!_mi_free_delayed_block(block)) {\n      // we might already start delayed freeing while another thread has not yet\n      // reset the delayed_freeing flag; in that case delay it further by reinserting the current block\n      // into the delayed free list\n      all_freed = false;\n      mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);\n      do {\n        mi_block_set_nextx(heap, block, dfree, heap->keys);\n      } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block));\n    }\n    block = next;\n  }\n  return all_freed;\n}\n\n/* -----------------------------------------------------------\n  Unfull, abandon, free and retire\n----------------------------------------------------------- */\n\n// Move a page from the full list back to a regular list\nvoid _mi_page_unfull(mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(_mi_page_is_valid(page));\n  mi_assert_internal(mi_page_is_in_full(page));\n  if (!mi_page_is_in_full(page)) return;\n\n  mi_heap_t* heap = mi_page_heap(page);\n  mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL];\n  mi_page_set_in_full(page, false); // to get the right queue\n  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);\n  mi_page_set_in_full(page, true);\n  mi_page_queue_enqueue_from_full(pq, pqfull, page);\n}\n\nstatic void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {\n  mi_assert_internal(pq == mi_page_queue_of(page));\n  mi_assert_internal(!mi_page_immediate_available(page));\n  mi_assert_internal(!mi_page_is_in_full(page));\n\n  if (mi_page_is_in_full(page)) return;\n  mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page);\n  _mi_page_free_collect(page,false);  // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set\n}\n\n\n// Abandon a page with used blocks at the end of a thread.\n// Note: only call if it is ensured that no references exist from\n// the `page->heap->thread_delayed_free` into this page.\n// Currently only called through `mi_heap_collect_ex` which ensures this.\nvoid _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(_mi_page_is_valid(page));\n  mi_assert_internal(pq == mi_page_queue_of(page));\n  mi_assert_internal(mi_page_heap(page) != NULL);\n\n  mi_heap_t* pheap = mi_page_heap(page);\n\n  // remove from our page list\n  mi_segments_tld_t* segments_tld = &pheap->tld->segments;\n  mi_page_queue_remove(pq, page);\n\n  // page is no longer associated with our heap\n  mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);\n  mi_page_set_heap(page, NULL);\n\n#if (MI_DEBUG>1) && !MI_TRACK_ENABLED\n  // check there are no references left..\n  for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) {\n    mi_assert_internal(_mi_ptr_page(block) != page);\n  }\n#endif\n\n  // and abandon it\n  mi_assert_internal(mi_page_heap(page) == NULL);\n  _mi_segment_page_abandon(page,segments_tld);\n}\n\n// force abandon a page\nvoid _mi_page_force_abandon(mi_page_t* page) {\n  mi_heap_t* heap = mi_page_heap(page);\n  // mark page as not using delayed free\n  _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);\n\n  // ensure this page is no longer in the heap delayed free list\n  _mi_heap_delayed_free_all(heap);\n  // We can still access the page meta-info even if it is freed as we ensure\n  // in `mi_segment_force_abandon` that the segment is not freed (yet)\n  if (page->capacity == 0) return; // it may have been freed now\n\n  // and now unlink it from the page queue and abandon (or free)\n  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);\n  if (mi_page_all_free(page)) {\n    _mi_page_free(page, pq, false);\n  }\n  else {\n    _mi_page_abandon(page, pq);\n  }\n}\n\n\n// Free a page with no more free blocks\nvoid _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(_mi_page_is_valid(page));\n  mi_assert_internal(pq == mi_page_queue_of(page));\n  mi_assert_internal(mi_page_all_free(page));\n  mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING);\n\n  // no more aligned blocks in here\n  mi_page_set_has_aligned(page, false);\n\n  // remove from the page list\n  // (no need to do _mi_heap_delayed_free first as all blocks are already free)\n  mi_heap_t* heap = mi_page_heap(page);\n  mi_segments_tld_t* segments_tld = &heap->tld->segments;\n  mi_page_queue_remove(pq, page);\n\n  // and free it  \n  mi_page_set_heap(page,NULL);\n  _mi_segment_page_free(page, force, segments_tld);\n}\n\n#define MI_MAX_RETIRE_SIZE    MI_MEDIUM_OBJ_SIZE_MAX   // should be less than size for MI_BIN_HUGE\n#define MI_RETIRE_CYCLES      (16)\n\n// Retire a page with no more used blocks\n// Important to not retire too quickly though as new\n// allocations might coming.\n// Note: called from `mi_free` and benchmarks often\n// trigger this due to freeing everything and then\n// allocating again so careful when changing this.\nvoid _mi_page_retire(mi_page_t* page) mi_attr_noexcept {\n  mi_assert_internal(page != NULL);\n  mi_assert_expensive(_mi_page_is_valid(page));\n  mi_assert_internal(mi_page_all_free(page));\n\n  mi_page_set_has_aligned(page, false);\n\n  // don't retire too often..\n  // (or we end up retiring and re-allocating most of the time)\n  // NOTE: refine this more: we should not retire if this\n  // is the only page left with free blocks. It is not clear\n  // how to check this efficiently though...\n  // for now, we don't retire if it is the only page left of this size class.\n  mi_page_queue_t* pq = mi_page_queue_of(page);\n  #if MI_RETIRE_CYCLES > 0\n  const size_t bsize = mi_page_block_size(page);\n  if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) {  // not full or huge queue?\n    if (pq->last==page && pq->first==page) { // the only page in the queue?\n      mi_stat_counter_increase(_mi_stats_main.pages_retire,1);\n      page->retire_expire = (bsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);\n      mi_heap_t* heap = mi_page_heap(page);\n      mi_assert_internal(pq >= heap->pages);\n      const size_t index = pq - heap->pages;\n      mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE);\n      if (index < heap->page_retired_min) heap->page_retired_min = index;\n      if (index > heap->page_retired_max) heap->page_retired_max = index;\n      mi_assert_internal(mi_page_all_free(page));\n      return; // don't free after all\n    }\n  }\n  #endif\n  _mi_page_free(page, pq, false);\n}\n\n// free retired pages: we don't need to look at the entire queues\n// since we only retire pages that are at the head position in a queue.\nvoid _mi_heap_collect_retired(mi_heap_t* heap, bool force) {\n  size_t min = MI_BIN_FULL;\n  size_t max = 0;\n  for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) {\n    mi_page_queue_t* pq   = &heap->pages[bin];\n    mi_page_t*       page = pq->first;\n    if (page != NULL && page->retire_expire != 0) {\n      if (mi_page_all_free(page)) {\n        page->retire_expire--;\n        if (force || page->retire_expire == 0) {\n          _mi_page_free(pq->first, pq, force);\n        }\n        else {\n          // keep retired, update min/max\n          if (bin < min) min = bin;\n          if (bin > max) max = bin;\n        }\n      }\n      else {\n        page->retire_expire = 0;\n      }\n    }\n  }\n  heap->page_retired_min = min;\n  heap->page_retired_max = max;\n}\n\n\n/* -----------------------------------------------------------\n  Initialize the initial free list in a page.\n  In secure mode we initialize a randomized list by\n  alternating between slices.\n----------------------------------------------------------- */\n\n#define MI_MAX_SLICE_SHIFT  (6)   // at most 64 slices\n#define MI_MAX_SLICES       (1UL << MI_MAX_SLICE_SHIFT)\n#define MI_MIN_SLICES       (2)\n\nstatic void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) {\n  MI_UNUSED(stats);\n  #if (MI_SECURE<=2)\n  mi_assert_internal(page->free == NULL);\n  mi_assert_internal(page->local_free == NULL);\n  #endif\n  mi_assert_internal(page->capacity + extend <= page->reserved);\n  mi_assert_internal(bsize == mi_page_block_size(page));\n  void* const page_area = mi_page_start(page);\n\n  // initialize a randomized free list\n  // set up `slice_count` slices to alternate between\n  size_t shift = MI_MAX_SLICE_SHIFT;\n  while ((extend >> shift) == 0) {\n    shift--;\n  }\n  const size_t slice_count = (size_t)1U << shift;\n  const size_t slice_extend = extend / slice_count;\n  mi_assert_internal(slice_extend >= 1);\n  mi_block_t* blocks[MI_MAX_SLICES];   // current start of the slice\n  size_t      counts[MI_MAX_SLICES];   // available objects in the slice\n  for (size_t i = 0; i < slice_count; i++) {\n    blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend);\n    counts[i] = slice_extend;\n  }\n  counts[slice_count-1] += (extend % slice_count);  // final slice holds the modulus too (todo: distribute evenly?)\n\n  // and initialize the free list by randomly threading through them\n  // set up first element\n  const uintptr_t r = _mi_heap_random_next(heap);\n  size_t current = r % slice_count;\n  counts[current]--;\n  mi_block_t* const free_start = blocks[current];\n  // and iterate through the rest; use `random_shuffle` for performance\n  uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0\n  for (size_t i = 1; i < extend; i++) {\n    // call random_shuffle only every INTPTR_SIZE rounds\n    const size_t round = i%MI_INTPTR_SIZE;\n    if (round == 0) rnd = _mi_random_shuffle(rnd);\n    // select a random next slice index\n    size_t next = ((rnd >> 8*round) & (slice_count-1));\n    while (counts[next]==0) {                            // ensure it still has space\n      next++;\n      if (next==slice_count) next = 0;\n    }\n    // and link the current block to it\n    counts[next]--;\n    mi_block_t* const block = blocks[current];\n    blocks[current] = (mi_block_t*)((uint8_t*)block + bsize);  // bump to the following block\n    mi_block_set_next(page, block, blocks[next]);   // and set next; note: we may have `current == next`\n    current = next;\n  }\n  // prepend to the free list (usually NULL)\n  mi_block_set_next(page, blocks[current], page->free);  // end of the list\n  page->free = free_start;\n}\n\nstatic mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats)\n{\n  MI_UNUSED(stats);\n  #if (MI_SECURE <= 2)\n  mi_assert_internal(page->free == NULL);\n  mi_assert_internal(page->local_free == NULL);\n  #endif\n  mi_assert_internal(page->capacity + extend <= page->reserved);\n  mi_assert_internal(bsize == mi_page_block_size(page));\n  void* const page_area = mi_page_start(page);\n\n  mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity);\n\n  // initialize a sequential free list\n  mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1);\n  mi_block_t* block = start;\n  while(block <= last) {\n    mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize);\n    mi_block_set_next(page,block,next);\n    block = next;\n  }\n  // prepend to free list (usually `NULL`)\n  mi_block_set_next(page, last, page->free);\n  page->free = start;\n}\n\n/* -----------------------------------------------------------\n  Page initialize and extend the capacity\n----------------------------------------------------------- */\n\n#define MI_MAX_EXTEND_SIZE    (4*1024)      // heuristic, one OS page seems to work well.\n#if (MI_SECURE>0)\n#define MI_MIN_EXTEND         (8*MI_SECURE) // extend at least by this many\n#else\n#define MI_MIN_EXTEND         (4)\n#endif\n\n// Extend the capacity (up to reserved) by initializing a free list\n// We do at most `MI_MAX_EXTEND` to avoid touching too much memory\n// Note: we also experimented with \"bump\" allocation on the first\n// allocations but this did not speed up any benchmark (due to an\n// extra test in malloc? or cache effects?)\nstatic bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {\n  mi_assert_expensive(mi_page_is_valid_init(page));\n  #if (MI_SECURE<=2)\n  mi_assert(page->free == NULL);\n  mi_assert(page->local_free == NULL);\n  if (page->free != NULL) return true;\n  #endif\n  if (page->capacity >= page->reserved) return true;\n\n  mi_stat_counter_increase(tld->stats.pages_extended, 1);\n\n  // calculate the extend count\n  const size_t bsize = mi_page_block_size(page);\n  size_t extend = page->reserved - page->capacity;\n  mi_assert_internal(extend > 0);\n\n  size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/bsize);\n  if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; }\n  mi_assert_internal(max_extend > 0);\n\n  if (extend > max_extend) {\n    // ensure we don't touch memory beyond the page to reduce page commit.\n    // the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%.\n    extend = max_extend;\n  }\n\n  mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved);\n  mi_assert_internal(extend < (1UL<<16));\n\n  // and append the extend the free list\n  if (extend < MI_MIN_SLICES || MI_SECURE==0) { //!mi_option_is_enabled(mi_option_secure)) {\n    mi_page_free_list_extend(page, bsize, extend, &tld->stats );\n  }\n  else {\n    mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats);\n  }\n  // enable the new free list\n  page->capacity += (uint16_t)extend;\n  mi_stat_increase(tld->stats.page_committed, extend * bsize);\n  mi_assert_expensive(mi_page_is_valid_init(page));\n  return true;\n}\n\n// Initialize a fresh page\nstatic void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) {\n  mi_assert(page != NULL);\n  mi_segment_t* segment = _mi_page_segment(page);\n  mi_assert(segment != NULL);\n  mi_assert_internal(block_size > 0);\n  // set fields\n  mi_page_set_heap(page, heap);\n  page->block_size = block_size;\n  size_t page_size;\n  page->page_start = _mi_segment_page_start(segment, page, &page_size);\n  mi_track_mem_noaccess(page->page_start,page_size);\n  mi_assert_internal(mi_page_block_size(page) <= page_size);\n  mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE);\n  mi_assert_internal(page_size / block_size < (1L<<16));\n  page->reserved = (uint16_t)(page_size / block_size);\n  mi_assert_internal(page->reserved > 0);\n  #if (MI_PADDING || MI_ENCODE_FREELIST)\n  page->keys[0] = _mi_heap_random_next(heap);\n  page->keys[1] = _mi_heap_random_next(heap);\n  #endif\n  page->free_is_zero = page->is_zero_init;\n  #if MI_DEBUG>2\n  if (page->is_zero_init) {\n    mi_track_mem_defined(page->page_start, page_size);\n    mi_assert_expensive(mi_mem_is_zero(page->page_start, page_size));\n  }\n  #endif\n  mi_assert_internal(page->is_committed);\n  if (block_size > 0 && _mi_is_power_of_two(block_size)) {\n    page->block_size_shift = (uint8_t)(mi_ctz((uintptr_t)block_size));\n  }\n  else {\n    page->block_size_shift = 0;\n  }\n\n  mi_assert_internal(page->capacity == 0);\n  mi_assert_internal(page->free == NULL);\n  mi_assert_internal(page->used == 0);\n  mi_assert_internal(page->xthread_free == 0);\n  mi_assert_internal(page->next == NULL);\n  mi_assert_internal(page->prev == NULL);\n  mi_assert_internal(page->retire_expire == 0);\n  mi_assert_internal(!mi_page_has_aligned(page));\n  #if (MI_PADDING || MI_ENCODE_FREELIST)\n  mi_assert_internal(page->keys[0] != 0);\n  mi_assert_internal(page->keys[1] != 0);\n  #endif\n  mi_assert_internal(page->block_size_shift == 0 || (block_size == ((size_t)1 << page->block_size_shift)));\n  mi_assert_expensive(mi_page_is_valid_init(page));\n\n  // initialize an initial free list\n  if (mi_page_extend_free(heap,page,tld)) {\n    mi_assert(mi_page_immediate_available(page));\n  }\n  return;\n}\n\n\n/* -----------------------------------------------------------\n  Find pages with free blocks\n-------------------------------------------------------------*/\n\n// search for a best next page to use for at most N pages (often cut short if immediate blocks are available)\n#define MI_MAX_CANDIDATE_SEARCH  (4)\n\n// is the page not yet used up to its reserved space?\nstatic bool mi_page_is_expandable(const mi_page_t* page) {\n  mi_assert_internal(page != NULL);\n  mi_assert_internal(page->capacity <= page->reserved);\n  return (page->capacity < page->reserved);\n}\n\n\n// Find a page with free blocks of `page->block_size`.\nstatic mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try)\n{\n  // search through the pages in \"next fit\" order\n  #if MI_STAT\n  size_t count = 0;\n  #endif\n  size_t candidate_count = 0;        // we reset this on the first candidate to limit the search\n  mi_page_t* page_candidate = NULL;  // a page with free space\n  mi_page_t* page = pq->first;\n\n  while (page != NULL)\n  {\n    mi_page_t* next = page->next; // remember next\n    #if MI_STAT\n    count++;\n    #endif\n    candidate_count++;\n\n    // collect freed blocks by us and other threads\n    _mi_page_free_collect(page, false);\n\n  #if MI_MAX_CANDIDATE_SEARCH > 1\n    // search up to N pages for a best candidate\n\n    // is the local free list non-empty?\n    const bool immediate_available = mi_page_immediate_available(page);\n\n    // if the page is completely full, move it to the `mi_pages_full`\n    // queue so we don't visit long-lived pages too often.\n    if (!immediate_available && !mi_page_is_expandable(page)) {\n      mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));\n      mi_page_to_full(page, pq);\n    }\n    else {\n      // the page has free space, make it a candidate\n      // we prefer non-expandable pages with high usage as candidates (to reduce commit, and increase chances of free-ing up pages)\n      if (page_candidate == NULL) {\n        page_candidate = page;\n        candidate_count = 0;\n      }\n      // prefer to reuse fuller pages (in the hope the less used page gets freed)\n      else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page) && !mi_page_is_expandable(page)) {\n        page_candidate = page;\n      }\n      // if we find a non-expandable candidate, or searched for N pages, return with the best candidate\n      if (immediate_available || candidate_count > MI_MAX_CANDIDATE_SEARCH) {\n        mi_assert_internal(page_candidate!=NULL);\n        break;\n      }\n    }\n  #else\n    // first-fit algorithm\n    // If the page contains free blocks, we are done\n    if (mi_page_immediate_available(page) || mi_page_is_expandable(page)) {\n      break;  // pick this one\n    }\n\n    // If the page is completely full, move it to the `mi_pages_full`\n    // queue so we don't visit long-lived pages too often.\n    mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));\n    mi_page_to_full(page, pq);\n  #endif\n\n    page = next;\n  } // for each page\n\n  mi_heap_stat_counter_increase(heap, page_searches, count);\n  mi_heap_stat_counter_increase(heap, page_searches_count, 1);\n\n  // set the page to the best candidate\n  if (page_candidate != NULL) {\n    page = page_candidate;\n  }\n  if (page != NULL) {\n    if (!mi_page_immediate_available(page)) {\n      mi_assert_internal(mi_page_is_expandable(page));\n      if (!mi_page_extend_free(heap, page, heap->tld)) {\n        page = NULL; // failed to extend\n      }\n    }\n    mi_assert_internal(page == NULL || mi_page_immediate_available(page));\n  }\n\n  if (page == NULL) {\n    _mi_heap_collect_retired(heap, false); // perhaps make a page available?\n    page = mi_page_fresh(heap, pq);\n    if (page == NULL && first_try) {\n      // out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again\n      page = mi_page_queue_find_free_ex(heap, pq, false);\n    }\n  }\n  else {\n    // move the page to the front of the queue\n    mi_page_queue_move_to_front(heap, pq, page);\n    page->retire_expire = 0;\n    // _mi_heap_collect_retired(heap, false); // update retire counts; note: increases rss on MemoryLoad bench so don't do this\n  }\n  mi_assert_internal(page == NULL || mi_page_immediate_available(page));\n\n\n  return page;\n}\n\n\n\n// Find a page with free blocks of `size`.\nstatic inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {\n  mi_page_queue_t* pq = mi_page_queue(heap, size);\n\n  // check the first page: we even do this with candidate search or otherwise we re-search every time\n  mi_page_t* page = pq->first;\n  if (page != NULL) {\n   #if (MI_SECURE>=3) // in secure mode, we extend half the time to increase randomness\n    if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) {\n      mi_page_extend_free(heap, page, heap->tld);\n      mi_assert_internal(mi_page_immediate_available(page));\n    }\n    else\n   #endif\n    {\n      _mi_page_free_collect(page,false);\n    }\n\n    if (mi_page_immediate_available(page)) {\n      page->retire_expire = 0;\n      return page; // fast path\n    }\n  }\n\n  return mi_page_queue_find_free_ex(heap, pq, true);\n}\n\n\n/* -----------------------------------------------------------\n  Users can register a deferred free function called\n  when the `free` list is empty. Since the `local_free`\n  is separate this is deterministically called after\n  a certain number of allocations.\n----------------------------------------------------------- */\n\nstatic mi_deferred_free_fun* volatile deferred_free = NULL;\nstatic _Atomic(void*) deferred_arg; // = NULL\n\nvoid _mi_deferred_free(mi_heap_t* heap, bool force) {\n  heap->tld->heartbeat++;\n  if (deferred_free != NULL && !heap->tld->recurse) {\n    heap->tld->recurse = true;\n    deferred_free(force, heap->tld->heartbeat, mi_atomic_load_ptr_relaxed(void,&deferred_arg));\n    heap->tld->recurse = false;\n  }\n}\n\nvoid mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept {\n  deferred_free = fn;\n  mi_atomic_store_ptr_release(void,&deferred_arg, arg);\n}\n\n\n/* -----------------------------------------------------------\n  General allocation\n----------------------------------------------------------- */\n\n// Large and huge page allocation.\n// Huge pages contain just one block, and the segment contains just that page (as `MI_SEGMENT_HUGE`).\n// Huge pages are also use if the requested alignment is very large (> MI_BLOCK_ALIGNMENT_MAX)\n// so their size is not always `> MI_LARGE_OBJ_SIZE_MAX`.\nstatic mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) {\n  size_t block_size = _mi_os_good_alloc_size(size);\n  mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0);\n  bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX || page_alignment > 0);\n  #if MI_HUGE_PAGE_ABANDON\n  mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size));\n  #else\n  mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_LARGE_OBJ_SIZE_MAX+1 : block_size);\n  mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq));\n  #endif\n  mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment);\n  if (page != NULL) {\n    mi_assert_internal(mi_page_immediate_available(page));\n\n    if (is_huge) {\n      mi_assert_internal(mi_page_is_huge(page));\n      mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE);\n      mi_assert_internal(_mi_page_segment(page)->used==1);\n      #if MI_HUGE_PAGE_ABANDON\n      mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue\n      mi_page_set_heap(page, NULL);\n      #endif\n    }\n    else {\n      mi_assert_internal(!mi_page_is_huge(page));\n    }\n\n    const size_t bsize = mi_page_usable_block_size(page);  // note: not `mi_page_block_size` to account for padding\n    /*if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {\n      mi_heap_stat_increase(heap, malloc_large, bsize);\n      mi_heap_stat_counter_increase(heap, malloc_large_count, 1);\n    }\n    else */\n    {\n      _mi_stat_increase(&heap->tld->stats.malloc_huge, bsize);\n      _mi_stat_counter_increase(&heap->tld->stats.malloc_huge_count, 1);\n    }\n  }\n  return page;\n}\n\n\n// Allocate a page\n// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.\nstatic mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignment) mi_attr_noexcept {\n  // huge allocation?\n  const size_t req_size = size - MI_PADDING_SIZE;  // correct for padding_size in case of an overflow on `size`\n  if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) {\n    if mi_unlikely(req_size > MI_MAX_ALLOC_SIZE) {\n      _mi_error_message(EOVERFLOW, \"allocation request is too large (%zu bytes)\\n\", req_size);\n      return NULL;\n    }\n    else {\n      return mi_large_huge_page_alloc(heap,size,huge_alignment);\n    }\n  }\n  else {\n    // otherwise find a page with free blocks in our size segregated queues\n    #if MI_PADDING\n    mi_assert_internal(size >= MI_PADDING_SIZE);\n    #endif\n    return mi_find_free_page(heap, size);\n  }\n}\n\n// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed.\n// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.\n// The `huge_alignment` is normally 0 but is set to a multiple of MI_SLICE_SIZE for\n// very large requested alignments in which case we use a huge singleton page.\nvoid* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, size_t* usable) mi_attr_noexcept\n{\n  mi_assert_internal(heap != NULL);\n\n  // initialize if necessary\n  if mi_unlikely(!mi_heap_is_initialized(heap)) {\n    heap = mi_heap_get_default(); // calls mi_thread_init\n    if mi_unlikely(!mi_heap_is_initialized(heap)) { return NULL; }\n  }\n  mi_assert_internal(mi_heap_is_initialized(heap));\n\n  // do administrative tasks every N generic mallocs\n  if mi_unlikely(++heap->generic_count >= 100) {\n    heap->generic_collect_count += heap->generic_count;\n    heap->generic_count = 0;\n    // call potential deferred free routines\n    _mi_deferred_free(heap, false);\n\n    // free delayed frees from other threads (but skip contended ones)\n    _mi_heap_delayed_free_partial(heap);\n\n    // collect every once in a while (10000 by default)\n    const long generic_collect = mi_option_get_clamp(mi_option_generic_collect, 1, 1000000L);\n    if (heap->generic_collect_count >= generic_collect) {\n      heap->generic_collect_count = 0;\n      mi_heap_collect(heap, false /* force? */);\n    }\n  }\n\n  // find (or allocate) a page of the right size\n  mi_page_t* page = mi_find_page(heap, size, huge_alignment);\n  if mi_unlikely(page == NULL) { // first time out of memory, try to collect and retry the allocation once more\n    mi_heap_collect(heap, true /* force */);\n    page = mi_find_page(heap, size, huge_alignment);\n  }\n\n  if mi_unlikely(page == NULL) { // out of memory\n    const size_t req_size = size - MI_PADDING_SIZE;  // correct for padding_size in case of an overflow on `size`\n    _mi_error_message(ENOMEM, \"unable to allocate memory (%zu bytes)\\n\", req_size);\n    return NULL;\n  }\n\n  mi_assert_internal(mi_page_immediate_available(page));\n  mi_assert_internal(mi_page_block_size(page) >= size);\n\n  // and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc)\n  void* const p = _mi_page_malloc_zero(heap, page, size, zero, usable);\n  mi_assert_internal(p != NULL);\n\n  // move singleton pages to the full queue\n  if (page->reserved == page->used) {\n    mi_page_to_full(page, mi_page_queue_of(page));\n  }\n  return p;\n}\n"
  },
  {
    "path": "src/prim/emscripten/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2025, Microsoft Research, Daan Leijen, Alon Zakai\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// This file is included in `src/prim/prim.c`\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"\n\n// Design\n// ======\n//\n// mimalloc is built on top of emmalloc. emmalloc is a minimal allocator on top\n// of sbrk. The reason for having three layers here is that we want mimalloc to\n// be able to allocate and release system memory properly, the same way it would\n// when using VirtualAlloc on Windows or mmap on POSIX, and sbrk is too limited.\n// Specifically, sbrk can only go up and down, and not \"skip\" over regions, and\n// so we end up either never freeing memory to the system, or we can get stuck\n// with holes.\n//\n// Atm wasm generally does *not* free memory back the system: once grown, we do\n// not shrink back down (https://github.com/WebAssembly/design/issues/1397).\n// However, that is expected to improve\n// (https://github.com/WebAssembly/memory-control/blob/main/proposals/memory-control/Overview.md)\n// and so we do not want to bake those limitations in here.\n//\n// Even without that issue, we want our system allocator to handle holes, that\n// is, it should merge freed regions and allow allocating new content there of\n// the full size, etc., so that we do not waste space. That means that the\n// system allocator really does need to handle the general problem of allocating\n// and freeing variable-sized chunks of memory in a random order, like malloc/\n// free do. And so it makes sense to layer mimalloc on top of such an\n// implementation.\n//\n// emmalloc makes sense for the lower level because it is small and simple while\n// still fully handling merging of holes etc. It is not the most efficient\n// allocator, but our assumption is that mimalloc needs to be fast while the\n// system allocator underneath it is called much less frequently.\n//\n\n//---------------------------------------------\n// init\n//---------------------------------------------\n\nvoid _mi_prim_mem_init( mi_os_mem_config_t* config) {\n  config->page_size = 64*MI_KiB; // WebAssembly has a fixed page size: 64KiB\n  config->alloc_granularity = 16;\n  config->has_overcommit = false;\n  config->has_partial_free = false;\n  config->has_virtual_reserve = false;\n}\n\nextern void emmalloc_free(void*);\n\nint _mi_prim_free(void* addr, size_t size) {\n  if (size==0) return 0;\n  emmalloc_free(addr);\n  return 0;\n}\n\n\n//---------------------------------------------\n// Allocation\n//---------------------------------------------\n\nextern void* emmalloc_memalign(size_t alignment, size_t size);\n\n// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.\nint _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {\n  MI_UNUSED(try_alignment); MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr);\n  *is_large = false;\n  // TODO: Track the highest address ever seen; first uses of it are zeroes.\n  //       That assumes no one else uses sbrk but us (they could go up,\n  //       scribble, and then down), but we could assert on that perhaps.\n  *is_zero = false;\n  // emmalloc has a minimum alignment size.\n  #define MIN_EMMALLOC_ALIGN           8\n  if (try_alignment < MIN_EMMALLOC_ALIGN) {\n    try_alignment = MIN_EMMALLOC_ALIGN;\n  }\n  void* p = emmalloc_memalign(try_alignment, size);\n  *addr = p;\n  if (p == 0) {\n    return ENOMEM;\n  }\n  return 0;\n}\n\n\n//---------------------------------------------\n// Commit/Reset\n//---------------------------------------------\n\nint _mi_prim_commit(void* addr, size_t size, bool* is_zero) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  // See TODO above.\n  *is_zero = false;\n  return 0;\n}\n\nint _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  *needs_recommit = false;\n  return 0;\n}\n\nint _mi_prim_reset(void* addr, size_t size) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  return 0;\n}\n\nint _mi_prim_reuse(void* addr, size_t size) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  return 0;\n}\n\nint _mi_prim_protect(void* addr, size_t size, bool protect) {\n  MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);\n  return 0;\n}\n\n\n//---------------------------------------------\n// Huge pages and NUMA nodes\n//---------------------------------------------\n\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {\n  MI_UNUSED(hint_addr); MI_UNUSED(size); MI_UNUSED(numa_node);\n  *is_zero = true;\n  *addr = NULL;\n  return ENOSYS;\n}\n\nsize_t _mi_prim_numa_node(void) {\n  return 0;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  return 1;\n}\n\n\n//----------------------------------------------------------------\n// Clock\n//----------------------------------------------------------------\n\n#include <emscripten/html5.h>\n\nmi_msecs_t _mi_prim_clock_now(void) {\n  return emscripten_date_now();\n}\n\n\n//----------------------------------------------------------------\n// Process info\n//----------------------------------------------------------------\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo)\n{\n  // use defaults\n  MI_UNUSED(pinfo);\n}\n\n\n//----------------------------------------------------------------\n// Output\n//----------------------------------------------------------------\n\n#include <emscripten/console.h>\n\nvoid _mi_prim_out_stderr( const char* msg) {\n  emscripten_console_error(msg);\n}\n\n\n//----------------------------------------------------------------\n// Environment\n//----------------------------------------------------------------\n\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size) {\n  // For code size reasons, do not support environ customization for now.\n  MI_UNUSED(name);\n  MI_UNUSED(result);\n  MI_UNUSED(result_size);\n  return false;\n}\n\n\n//----------------------------------------------------------------\n// Random\n//----------------------------------------------------------------\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  int err = getentropy(buf, buf_len);\n  return !err;\n}\n\n\n//----------------------------------------------------------------\n// Thread init/done\n//----------------------------------------------------------------\n\n#if defined(MI_USE_PTHREADS)\n\n// use pthread local storage keys to detect thread ending\n// (and used with MI_TLS_PTHREADS for the default heap)\npthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);\n\nstatic void mi_pthread_done(void* value) {\n  if (value!=NULL) {\n    _mi_thread_done((mi_heap_t*)value);\n  }\n}\n\nvoid _mi_prim_thread_init_auto_done(void) {\n  mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));\n  pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);\n}\n\nvoid _mi_prim_thread_done_auto_done(void) {\n  // nothing to do\n}\n\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n  if (_mi_heap_default_key != (pthread_key_t)(-1)) {  // can happen during recursive invocation on freeBSD\n    pthread_setspecific(_mi_heap_default_key, heap);\n  }\n}\n\n#else\n\nvoid _mi_prim_thread_init_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_done_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n  MI_UNUSED(heap);\n\n}\n#endif\n"
  },
  {
    "path": "src/prim/osx/alloc-override-zone.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2022, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n\n#if defined(MI_MALLOC_OVERRIDE)\n\n#if !defined(__APPLE__)\n#error \"this file should only be included on macOS\"\n#endif\n\n/* ------------------------------------------------------\n   Override system malloc on macOS\n   This is done through the malloc zone interface.\n   It seems to be most robust in combination with interposing\n   though or otherwise we may get zone errors as there are could\n   be allocations done by the time we take over the\n   zone.\n------------------------------------------------------ */\n\n#include <AvailabilityMacros.h>\n#include <malloc/malloc.h>\n#include <string.h>  // memset\n#include <stdlib.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)\n// only available from OSX 10.6\nextern malloc_zone_t* malloc_default_purgeable_zone(void) __attribute__((weak_import));\n#endif\n\n/* ------------------------------------------------------\n   malloc zone members\n------------------------------------------------------ */\n\nstatic size_t zone_size(malloc_zone_t* zone, const void* p) {\n  MI_UNUSED(zone);\n  if (!mi_is_in_heap_region(p)){ return 0; } // not our pointer, bail out\n  return mi_usable_size(p);\n}\n\nstatic void* zone_malloc(malloc_zone_t* zone, size_t size) {\n  MI_UNUSED(zone);\n  return mi_malloc(size);\n}\n\nstatic void* zone_calloc(malloc_zone_t* zone, size_t count, size_t size) {\n  MI_UNUSED(zone);\n  return mi_calloc(count, size);\n}\n\nstatic void* zone_valloc(malloc_zone_t* zone, size_t size) {\n  MI_UNUSED(zone);\n  return mi_malloc_aligned(size, _mi_os_page_size());\n}\n\nstatic void zone_free(malloc_zone_t* zone, void* p) {\n  MI_UNUSED(zone);\n  mi_cfree(p);\n}\n\nstatic void* zone_realloc(malloc_zone_t* zone, void* p, size_t newsize) {\n  MI_UNUSED(zone);\n  return mi_realloc(p, newsize);\n}\n\nstatic void* zone_memalign(malloc_zone_t* zone, size_t alignment, size_t size) {\n  MI_UNUSED(zone);\n  return mi_malloc_aligned(size,alignment);\n}\n\nstatic void zone_destroy(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  // todo: ignore for now?\n}\n\nstatic unsigned zone_batch_malloc(malloc_zone_t* zone, size_t size, void** ps, unsigned count) {\n  unsigned i;\n  for (i = 0; i < count; i++) {\n    ps[i] = zone_malloc(zone, size);\n    if (ps[i] == NULL) break;\n  }\n  return i;\n}\n\nstatic void zone_batch_free(malloc_zone_t* zone, void** ps, unsigned count) {\n  for(size_t i = 0; i < count; i++) {\n    zone_free(zone, ps[i]);\n    ps[i] = NULL;\n  }\n}\n\nstatic size_t zone_pressure_relief(malloc_zone_t* zone, size_t size) {\n  MI_UNUSED(zone); MI_UNUSED(size);\n  mi_collect(false);\n  return 0;\n}\n\nstatic void zone_free_definite_size(malloc_zone_t* zone, void* p, size_t size) {\n  MI_UNUSED(size);\n  zone_free(zone,p);\n}\n\nstatic boolean_t zone_claimed_address(malloc_zone_t* zone, void* p) {\n  MI_UNUSED(zone);\n  return mi_is_in_heap_region(p);\n}\n\n\n/* ------------------------------------------------------\n   Introspection members\n------------------------------------------------------ */\n\nstatic kern_return_t intro_enumerator(task_t task, void* p,\n                            unsigned type_mask, vm_address_t zone_address,\n                            memory_reader_t reader,\n                            vm_range_recorder_t recorder)\n{\n  // todo: enumerate all memory\n  MI_UNUSED(task); MI_UNUSED(p); MI_UNUSED(type_mask); MI_UNUSED(zone_address);\n  MI_UNUSED(reader); MI_UNUSED(recorder);\n  return KERN_SUCCESS;\n}\n\nstatic size_t intro_good_size(malloc_zone_t* zone, size_t size) {\n  MI_UNUSED(zone);\n  return mi_good_size(size);\n}\n\nstatic boolean_t intro_check(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  return true;\n}\n\nstatic void intro_print(malloc_zone_t* zone, boolean_t verbose) {\n  MI_UNUSED(zone); MI_UNUSED(verbose);\n  mi_stats_print(NULL);\n}\n\nstatic void intro_log(malloc_zone_t* zone, void* p) {\n  MI_UNUSED(zone); MI_UNUSED(p);\n  // todo?\n}\n\nstatic void intro_force_lock(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  // todo?\n}\n\nstatic void intro_force_unlock(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  // todo?\n}\n\nstatic void intro_statistics(malloc_zone_t* zone, malloc_statistics_t* stats) {\n  MI_UNUSED(zone);\n  // todo...\n  stats->blocks_in_use = 0;\n  stats->size_in_use = 0;\n  stats->max_size_in_use = 0;\n  stats->size_allocated = 0;\n}\n\nstatic boolean_t intro_zone_locked(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  return false;\n}\n\n\n/* ------------------------------------------------------\n  At process start, override the default allocator\n------------------------------------------------------ */\n\n#if defined(__GNUC__) && !defined(__clang__)\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#endif\n\n#if defined(__clang__)\n#pragma clang diagnostic ignored \"-Wc99-extensions\"\n#endif\n\nstatic malloc_introspection_t mi_introspect = {\n  .enumerator = &intro_enumerator,\n  .good_size = &intro_good_size,\n  .check = &intro_check,\n  .print = &intro_print,\n  .log = &intro_log,\n  .force_lock = &intro_force_lock,\n  .force_unlock = &intro_force_unlock,\n#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) && !defined(__ppc__)\n  .statistics = &intro_statistics,\n  .zone_locked = &intro_zone_locked,\n#endif\n};\n\nstatic malloc_zone_t mi_malloc_zone = {\n  // note: even with designators, the order is important for C++ compilation\n  //.reserved1 = NULL,\n  //.reserved2 = NULL,\n  .size = &zone_size,\n  .malloc = &zone_malloc,\n  .calloc = &zone_calloc,\n  .valloc = &zone_valloc,\n  .free = &zone_free,\n  .realloc = &zone_realloc,\n  .destroy = &zone_destroy,\n  .zone_name = \"mimalloc\",\n  .batch_malloc = &zone_batch_malloc,\n  .batch_free = &zone_batch_free,\n  .introspect = &mi_introspect,\n#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) && !defined(__ppc__)\n  #if defined(MAC_OS_X_VERSION_10_14) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)\n  .version = 10,\n  #else\n  .version = 9,\n  #endif\n  // switch to version 9+ on OSX 10.6 to support memalign.\n  .memalign = &zone_memalign,\n  .free_definite_size = &zone_free_definite_size,\n  #if defined(MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)\n  .pressure_relief = &zone_pressure_relief,\n  #endif\n  #if defined(MAC_OS_X_VERSION_10_14) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)\n  .claimed_address = &zone_claimed_address,\n  #endif\n#else\n  .version = 4,\n#endif\n};\n\n#ifdef __cplusplus\n}\n#endif\n\n\n#if defined(MI_OSX_INTERPOSE) && defined(MI_SHARED_LIB_EXPORT)\n\n// ------------------------------------------------------\n// Override malloc_xxx and malloc_zone_xxx api's to use only\n// our mimalloc zone. Since even the loader uses malloc\n// on macOS, this ensures that all allocations go through\n// mimalloc (as all calls are interposed).\n// The main `malloc`, `free`, etc calls are interposed in `alloc-override.c`,\n// Here, we also override macOS specific API's like\n// `malloc_zone_calloc` etc. see <https://github.com/aosm/libmalloc/blob/master/man/malloc_zone_malloc.3>\n// ------------------------------------------------------\n\nstatic inline malloc_zone_t* mi_get_default_zone(void)\n{\n  static bool init;\n  if mi_unlikely(!init) {\n    init = true;\n    malloc_zone_register(&mi_malloc_zone);  // by calling register we avoid a zone error on free (see <http://eatmyrandom.blogspot.com/2010/03/mallocfree-interception-on-mac-os-x.html>)\n  }\n  return &mi_malloc_zone;\n}\n\nmi_decl_externc int  malloc_jumpstart(uintptr_t cookie);\nmi_decl_externc void _malloc_fork_prepare(void);\nmi_decl_externc void _malloc_fork_parent(void);\nmi_decl_externc void _malloc_fork_child(void);\n\n\nstatic malloc_zone_t* mi_malloc_create_zone(vm_size_t size, unsigned flags) {\n  MI_UNUSED(size); MI_UNUSED(flags);\n  return mi_get_default_zone();\n}\n\nstatic malloc_zone_t* mi_malloc_default_zone (void) {\n  return mi_get_default_zone();\n}\n\nstatic malloc_zone_t* mi_malloc_default_purgeable_zone(void) {\n  return mi_get_default_zone();\n}\n\nstatic void mi_malloc_destroy_zone(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  // nothing.\n}\n\nstatic kern_return_t mi_malloc_get_all_zones (task_t task, memory_reader_t mr, vm_address_t** addresses, unsigned* count) {\n  MI_UNUSED(task); MI_UNUSED(mr);\n  if (addresses != NULL) *addresses = NULL;\n  if (count != NULL) *count = 0;\n  return KERN_SUCCESS;\n}\n\nstatic const char* mi_malloc_get_zone_name(malloc_zone_t* zone) {\n  return (zone == NULL ? mi_malloc_zone.zone_name : zone->zone_name);\n}\n\nstatic void mi_malloc_set_zone_name(malloc_zone_t* zone, const char* name) {\n  MI_UNUSED(zone); MI_UNUSED(name);\n}\n\nstatic int mi_malloc_jumpstart(uintptr_t cookie) {\n  MI_UNUSED(cookie);\n  return 1; // or 0 for no error?\n}\n\nstatic void mi__malloc_fork_prepare(void) {\n  // nothing\n}\nstatic void mi__malloc_fork_parent(void) {\n  // nothing\n}\nstatic void mi__malloc_fork_child(void) {\n  // nothing\n}\n\nstatic void mi_malloc_printf(const char* fmt, ...) {\n  MI_UNUSED(fmt);\n}\n\nstatic bool zone_check(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n  return true;\n}\n\nstatic malloc_zone_t* zone_from_ptr(const void* p) {\n  MI_UNUSED(p);\n  return mi_get_default_zone();\n}\n\nstatic void zone_log(malloc_zone_t* zone, void* p) {\n  MI_UNUSED(zone); MI_UNUSED(p);\n}\n\nstatic void zone_print(malloc_zone_t* zone, bool b) {\n  MI_UNUSED(zone); MI_UNUSED(b);\n}\n\nstatic void zone_print_ptr_info(void* p) {\n  MI_UNUSED(p);\n}\n\nstatic void zone_register(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n}\n\nstatic void zone_unregister(malloc_zone_t* zone) {\n  MI_UNUSED(zone);\n}\n\n// use interposing so `DYLD_INSERT_LIBRARIES` works without `DYLD_FORCE_FLAT_NAMESPACE=1`\n// See: <https://books.google.com/books?id=K8vUkpOXhN4C&pg=PA73>\nstruct mi_interpose_s {\n  const void* replacement;\n  const void* target;\n};\n#define MI_INTERPOSE_FUN(oldfun,newfun) { (const void*)&newfun, (const void*)&oldfun }\n#define MI_INTERPOSE_MI(fun)            MI_INTERPOSE_FUN(fun,mi_##fun)\n#define MI_INTERPOSE_ZONE(fun)          MI_INTERPOSE_FUN(malloc_##fun,fun)\n__attribute__((used)) static const struct mi_interpose_s _mi_zone_interposes[]  __attribute__((section(\"__DATA, __interpose\"))) =\n{\n\n  MI_INTERPOSE_MI(malloc_create_zone),\n  MI_INTERPOSE_MI(malloc_default_purgeable_zone),\n  MI_INTERPOSE_MI(malloc_default_zone),\n  MI_INTERPOSE_MI(malloc_destroy_zone),\n  MI_INTERPOSE_MI(malloc_get_all_zones),\n  MI_INTERPOSE_MI(malloc_get_zone_name),\n  MI_INTERPOSE_MI(malloc_jumpstart),\n  MI_INTERPOSE_MI(malloc_printf),\n  MI_INTERPOSE_MI(malloc_set_zone_name),\n  MI_INTERPOSE_MI(_malloc_fork_child),\n  MI_INTERPOSE_MI(_malloc_fork_parent),\n  MI_INTERPOSE_MI(_malloc_fork_prepare),\n\n  MI_INTERPOSE_ZONE(zone_batch_free),\n  MI_INTERPOSE_ZONE(zone_batch_malloc),\n  MI_INTERPOSE_ZONE(zone_calloc),\n  MI_INTERPOSE_ZONE(zone_check),\n  MI_INTERPOSE_ZONE(zone_free),\n  MI_INTERPOSE_ZONE(zone_from_ptr),\n  MI_INTERPOSE_ZONE(zone_log),\n  MI_INTERPOSE_ZONE(zone_malloc),\n  MI_INTERPOSE_ZONE(zone_memalign),\n  MI_INTERPOSE_ZONE(zone_print),\n  MI_INTERPOSE_ZONE(zone_print_ptr_info),\n  MI_INTERPOSE_ZONE(zone_realloc),\n  MI_INTERPOSE_ZONE(zone_register),\n  MI_INTERPOSE_ZONE(zone_unregister),\n  MI_INTERPOSE_ZONE(zone_valloc)\n};\n\n\n#else\n\n// ------------------------------------------------------\n// hook into the zone api's without interposing\n// This is the official way of adding an allocator but\n// it seems less robust than using interpose.\n// ------------------------------------------------------\n\nstatic inline malloc_zone_t* mi_get_default_zone(void)\n{\n  // The first returned zone is the real default\n  malloc_zone_t** zones = NULL;\n  unsigned count = 0;\n  kern_return_t ret = malloc_get_all_zones(0, NULL, (vm_address_t**)&zones, &count);\n  if (ret == KERN_SUCCESS && count > 0) {\n    return zones[0];\n  }\n  else {\n    // fallback\n    return malloc_default_zone();\n  }\n}\n\n#if defined(__clang__)\n__attribute__((constructor(101))) // highest priority\n#else\n__attribute__((constructor))      // priority level is not supported by gcc\n#endif\n__attribute__((used))\nstatic void _mi_macos_override_malloc(void) {\n  malloc_zone_t* purgeable_zone = NULL;\n\n  #if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)\n  // force the purgeable zone to exist to avoid strange bugs\n  if (malloc_default_purgeable_zone) {\n    purgeable_zone = malloc_default_purgeable_zone();\n  }\n  #endif\n\n  // Register our zone.\n  // thomcc: I think this is still needed to put us in the zone list.\n  malloc_zone_register(&mi_malloc_zone);\n  // Unregister the default zone, this makes our zone the new default\n  // as that was the last registered.\n  malloc_zone_t *default_zone = mi_get_default_zone();\n  // thomcc: Unsure if the next test is *always* false or just false in the\n  // cases I've tried. I'm also unsure if the code inside is needed. at all\n  if (default_zone != &mi_malloc_zone) {\n    malloc_zone_unregister(default_zone);\n\n    // Reregister the default zone so free and realloc in that zone keep working.\n    malloc_zone_register(default_zone);\n  }\n\n  // Unregister, and re-register the purgeable_zone to avoid bugs if it occurs\n  // earlier than the default zone.\n  if (purgeable_zone != NULL) {\n    malloc_zone_unregister(purgeable_zone);\n    malloc_zone_register(purgeable_zone);\n  }\n\n}\n#endif  // MI_OSX_INTERPOSE\n\n#endif // MI_MALLOC_OVERRIDE\n"
  },
  {
    "path": "src/prim/osx/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// We use the unix/prim.c with the mmap API on macOSX\n#include \"../unix/prim.c\"\n"
  },
  {
    "path": "src/prim/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// Select the implementation of the primitives\n// depending on the OS.\n\n#if defined(_WIN32)\n#include \"windows/prim.c\"  // VirtualAlloc (Windows)\n\n#elif defined(__APPLE__)\n#include \"osx/prim.c\"      // macOSX (actually defers to mmap in unix/prim.c)\n\n#elif defined(__wasi__)\n#define MI_USE_SBRK\n#include \"wasi/prim.c\"     // memory-grow or sbrk (Wasm)\n\n#elif defined(__EMSCRIPTEN__)\n#include \"emscripten/prim.c\" // emmalloc_*, + pthread support\n\n#else\n#include \"unix/prim.c\"     // mmap() (Linux, macOSX, BSD, Illumnos, Haiku, DragonFly, etc.)\n\n#endif\n\n// Generic process initialization\n#ifndef MI_PRIM_HAS_PROCESS_ATTACH\n#if defined(__GNUC__) || defined(__clang__)\n  // gcc,clang: use the constructor/destructor attribute\n  // which for both seem to run before regular constructors/destructors\n  #if defined(__clang__)\n    #define mi_attr_constructor __attribute__((constructor(101)))\n    #define mi_attr_destructor  __attribute__((destructor(101)))\n  #else\n    #define mi_attr_constructor __attribute__((constructor))\n    #define mi_attr_destructor  __attribute__((destructor))\n  #endif\n  static void mi_attr_constructor mi_process_attach(void) {\n    _mi_auto_process_init();\n  }\n  static void mi_attr_destructor mi_process_detach(void) {\n    _mi_auto_process_done();\n  }\n#elif defined(__cplusplus)\n  // C++: use static initialization to detect process start/end\n  // This is not guaranteed to be first/last but the best we can generally do?\n  struct mi_init_done_t {\n    mi_init_done_t() {\n      _mi_auto_process_init();\n    }\n    ~mi_init_done_t() {\n      _mi_auto_process_done();\n    }\n  };\n  static mi_init_done_t mi_init_done;\n #else\n  #pragma message(\"define a way to call _mi_auto_process_init/done on your platform\")\n#endif\n#endif\n\n// Generic allocator init/done callback\n#ifndef MI_PRIM_HAS_ALLOCATOR_INIT\nbool _mi_is_redirected(void) {\n  return false;\n}\nbool _mi_allocator_init(const char** message) {\n  if (message != NULL) { *message = NULL; }\n  return true;\n}\nvoid _mi_allocator_done(void) {\n  // nothing to do\n}\n#endif\n"
  },
  {
    "path": "src/prim/readme.md",
    "content": "## Portability Primitives\n\nThis is the portability layer where all primitives needed from the OS are defined.\n\n- `include/mimalloc/prim.h`: primitive portability API definition.\n- `prim.c`: Selects one of `unix/prim.c`, `wasi/prim.c`, or `windows/prim.c` depending on the host platform\n            (and on macOS, `osx/prim.c` defers to `unix/prim.c`).\n\nNote: still work in progress, there may still be places in the sources that still depend on OS ifdef's."
  },
  {
    "path": "src/prim/unix/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2025, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// This file is included in `src/prim/prim.c`\n\n#ifndef _DEFAULT_SOURCE\n#define _DEFAULT_SOURCE   // ensure mmap flags and syscall are defined\n#endif\n\n#if defined(__sun)\n// illumos provides new mman.h api when any of these are defined\n// otherwise the old api based on caddr_t which predates the void pointers one.\n// stock solaris provides only the former, chose to atomically to discard those\n// flags only here rather than project wide tough.\n#undef _XOPEN_SOURCE\n#undef _POSIX_C_SOURCE\n#endif\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"\n\n#include <sys/mman.h>  // mmap\n#include <unistd.h>    // sysconf\n#include <fcntl.h>     // open, close, read, access\n#include <stdlib.h>    // getenv, arc4random_buf\n\n#if defined(__linux__)\n  #include <features.h>\n  #include <sys/prctl.h>    // THP disable, PR_SET_VMA\n  #include <sys/sysinfo.h>  // sysinfo\n  #if defined(__GLIBC__) && !defined(PR_SET_VMA)\n  #include <linux/prctl.h>\n  #endif\n  #if defined(__GLIBC__)\n  #include <linux/mman.h>   // linux mmap flags\n  #else\n  #include <sys/mman.h>\n  #endif\n#elif defined(__APPLE__)\n  #include <AvailabilityMacros.h>\n  #include <TargetConditionals.h>\n  #if !defined(TARGET_OS_OSX) || TARGET_OS_OSX   // see issue #879, used to be (!TARGET_IOS_IPHONE && !TARGET_IOS_SIMULATOR)\n  #include <mach/vm_statistics.h>    // VM_MAKE_TAG, VM_FLAGS_SUPERPAGE_SIZE_2MB, etc.\n  #endif\n  #if !defined(MAC_OS_X_VERSION_10_7)\n  #define MAC_OS_X_VERSION_10_7   1070\n  #endif\n  #include <sys/sysctl.h>\n#elif defined(__FreeBSD__) || defined(__DragonFly__)\n  #include <sys/param.h>\n  #if __FreeBSD_version >= 1200000\n  #include <sys/cpuset.h>\n  #include <sys/domainset.h>\n  #endif\n  #include <sys/sysctl.h>\n#endif\n\n#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__)\n  #define MI_HAS_SYSCALL_H\n  #include <sys/syscall.h>\n#endif\n\n#if !defined(MADV_DONTNEED) && defined(POSIX_MADV_DONTNEED)  // QNX\n#define MADV_DONTNEED  POSIX_MADV_DONTNEED\n#endif\n#if !defined(MADV_FREE) && defined(POSIX_MADV_FREE)  // QNX\n#define MADV_FREE  POSIX_MADV_FREE\n#endif\n\n#define MI_UNIX_LARGE_PAGE_SIZE (2*MI_MiB) // TODO: can we query the OS for this?\n\n//------------------------------------------------------------------------------------\n// Use syscalls for some primitives to allow for libraries that override open/read/close etc.\n// and do allocation themselves; using syscalls prevents recursion when mimalloc is\n// still initializing (issue #713)\n// Declare inline to avoid unused function warnings.\n//------------------------------------------------------------------------------------\n\n#if defined(MI_HAS_SYSCALL_H) && defined(SYS_open) && defined(SYS_close) && defined(SYS_read) && defined(SYS_access)\n\nstatic inline int mi_prim_open(const char* fpath, int open_flags) {\n  return syscall(SYS_open,fpath,open_flags,0);\n}\nstatic inline ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {\n  return syscall(SYS_read,fd,buf,bufsize);\n}\nstatic inline int mi_prim_close(int fd) {\n  return syscall(SYS_close,fd);\n}\nstatic inline int mi_prim_access(const char *fpath, int mode) {\n  return syscall(SYS_access,fpath,mode);\n}\n\n#else\n\nstatic inline int mi_prim_open(const char* fpath, int open_flags) {\n  return open(fpath,open_flags);\n}\nstatic inline ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {\n  return read(fd,buf,bufsize);\n}\nstatic inline int mi_prim_close(int fd) {\n  return close(fd);\n}\nstatic inline int mi_prim_access(const char *fpath, int mode) {\n  return access(fpath,mode);\n}\n\n#endif\n\n\n\n//---------------------------------------------\n// init\n//---------------------------------------------\n\nstatic bool unix_detect_overcommit(void) {\n  bool os_overcommit = true;\n  #if defined(__linux__)\n    int fd = mi_prim_open(\"/proc/sys/vm/overcommit_memory\", O_RDONLY);\n    if (fd >= 0) {\n      char buf[32];\n      ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));\n      mi_prim_close(fd);\n      // <https://www.kernel.org/doc/Documentation/vm/overcommit-accounting>\n      // 0: heuristic overcommit, 1: always overcommit, 2: never overcommit (ignore NORESERVE)\n      if (nread >= 1) {\n        os_overcommit = (buf[0] == '0' || buf[0] == '1');\n      }\n    }\n  #elif defined(__FreeBSD__)\n    int val = 0;\n    size_t olen = sizeof(val);\n    if (sysctlbyname(\"vm.overcommit\", &val, &olen, NULL, 0) == 0) {\n      os_overcommit = (val != 0);\n    }\n  #else\n    // default: overcommit is true\n  #endif\n  return os_overcommit;\n}\n\n// try to detect the physical memory dynamically (if possible)\nstatic void unix_detect_physical_memory( size_t page_size, size_t* physical_memory_in_kib ) {\n  #if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE))  // freeBSD, macOS\n    MI_UNUSED(page_size);\n    int64_t physical_memory = 0;\n    size_t length = sizeof(int64_t);\n    #if defined(HW_PHYSMEM64)\n    int mib[2] = { CTL_HW, HW_PHYSMEM64 };\n    #else\n    int mib[2] = { CTL_HW, HW_MEMSIZE };\n    #endif\n    const int err = sysctl(mib, 2, &physical_memory, &length, NULL, 0);\n    if (err==0 && physical_memory > 0) {\n      const int64_t phys_in_kib = physical_memory / MI_KiB;\n      if (phys_in_kib > 0 && (uint64_t)phys_in_kib <= SIZE_MAX) {\n        *physical_memory_in_kib = (size_t)phys_in_kib;\n      }\n    }\n  #elif defined(__linux__)\n    MI_UNUSED(page_size);\n    struct sysinfo info; _mi_memzero_var(info);\n    const int err = sysinfo(&info);\n    if (err==0 && info.totalram > 0 && info.totalram <= SIZE_MAX) {\n      *physical_memory_in_kib = (size_t)info.totalram / MI_KiB;\n    }\n  #elif defined(_SC_PHYS_PAGES)  // do not use by default as it might cause allocation (by using `fopen` to parse /proc/meminfo) (issue #1100)\n    const long pphys = sysconf(_SC_PHYS_PAGES);\n    const size_t psize_in_kib = page_size / MI_KiB;\n    if (psize_in_kib > 0 && pphys > 0 && (unsigned long)pphys <= SIZE_MAX && (size_t)pphys <= (SIZE_MAX/psize_in_kib)) {\n      *physical_memory_in_kib = (size_t)pphys * psize_in_kib;\n    }\n  #endif\n}\n\nvoid _mi_prim_mem_init( mi_os_mem_config_t* config )\n{\n  long psize = sysconf(_SC_PAGESIZE);\n  if (psize > 0 && (unsigned long)psize < SIZE_MAX) {\n    config->page_size = (size_t)psize;\n    config->alloc_granularity = (size_t)psize;\n    unix_detect_physical_memory(config->page_size, &config->physical_memory_in_kib);\n  }\n  config->large_page_size = MI_UNIX_LARGE_PAGE_SIZE;\n  config->has_overcommit = unix_detect_overcommit();\n  config->has_partial_free = true;    // mmap can free in parts\n  config->has_virtual_reserve = true; // todo: check if this true for NetBSD?  (for anonymous mmap with PROT_NONE)\n\n  // disable transparent huge pages for this process?\n  #if (defined(__linux__) || defined(__ANDROID__)) && defined(PR_GET_THP_DISABLE)\n  #if defined(MI_NO_THP)\n  if (true)\n  #else\n  if (!mi_option_is_enabled(mi_option_allow_thp)) // disable THP if requested through an option\n  #endif\n  {\n    int val = 0;\n    if (prctl(PR_GET_THP_DISABLE, &val, 0, 0, 0) != 0) {\n      // Most likely since distros often come with always/madvise settings.\n      val = 1;\n      // Disabling only for mimalloc process rather than touching system wide settings\n      (void)prctl(PR_SET_THP_DISABLE, &val, 0, 0, 0);\n    }\n  }\n  #endif\n}\n\n\n//---------------------------------------------\n// free\n//---------------------------------------------\n\nint _mi_prim_free(void* addr, size_t size ) {\n  if (size==0) return 0;\n  bool err = (munmap(addr, size) == -1);\n  return (err ? errno : 0);\n}\n\n\n//---------------------------------------------\n// mmap\n//---------------------------------------------\n\nstatic int unix_madvise(void* addr, size_t size, int advice) {\n  #if defined(__sun)\n  int res = madvise((caddr_t)addr, size, advice);  // Solaris needs cast (issue #520)\n  #elif defined(__QNX__)\n  int res = posix_madvise(addr, size, advice);\n  #else\n  int res = madvise(addr, size, advice);\n  #endif\n  return (res==0 ? 0 : errno);\n}\n\nstatic void* unix_mmap_prim(void* addr, size_t size, int protect_flags, int flags, int fd) {\n  void* p = mmap(addr, size, protect_flags, flags, fd, 0 /* offset */);\n  #if defined(__linux__) && defined(PR_SET_VMA)\n  if (p!=MAP_FAILED && p!=NULL) {\n    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, p, size, \"mimalloc\");\n  }\n  #endif\n  return p;\n}\n\nstatic void* unix_mmap_prim_aligned(void* addr, size_t size, size_t try_alignment, int protect_flags, int flags, int fd) {\n  MI_UNUSED(try_alignment);\n  void* p = NULL;\n  #if defined(MAP_ALIGNED)  // BSD\n  if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {\n    size_t n = mi_bsr(try_alignment);\n    if (((size_t)1 << n) == try_alignment && n >= 12 && n <= 30) {  // alignment is a power of 2 and 4096 <= alignment <= 1GiB\n      p = unix_mmap_prim(addr, size, protect_flags, flags | MAP_ALIGNED(n), fd);\n      if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {\n        int err = errno;\n        _mi_trace_message(\"unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\\n\", err, err, size, try_alignment, addr);\n      }\n      if (p!=MAP_FAILED) return p;\n      // fall back to regular mmap\n    }\n  }\n  #elif defined(MAP_ALIGN)  // Solaris\n  if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {\n    p = unix_mmap_prim((void*)try_alignment, size, protect_flags, flags | MAP_ALIGN, fd);  // addr parameter is the required alignment\n    if (p!=MAP_FAILED) return p;\n    // fall back to regular mmap\n  }\n  #endif\n  #if (MI_INTPTR_SIZE >= 8) && !defined(MAP_ALIGNED)\n  // on 64-bit systems, use the virtual address area after 2TiB for 4MiB aligned allocations\n  if (addr == NULL) {\n    void* hint = _mi_os_get_aligned_hint(try_alignment, size);\n    if (hint != NULL) {\n      p = unix_mmap_prim(hint, size, protect_flags, flags, fd);\n      if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {\n        #if MI_TRACK_ENABLED  // asan sometimes does not instrument errno correctly?\n        int err = 0;\n        #else\n        int err = errno;\n        #endif\n        _mi_trace_message(\"unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\\n\", err, err, size, try_alignment, hint);\n      }\n      if (p!=MAP_FAILED) return p;\n      // fall back to regular mmap\n    }\n  }\n  #endif\n  // regular mmap\n  p = unix_mmap_prim(addr, size, protect_flags, flags, fd);\n  if (p!=MAP_FAILED) return p;\n  // failed to allocate\n  return NULL;\n}\n\nstatic int unix_mmap_fd(void) {\n  #if defined(VM_MAKE_TAG)\n  // macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99)\n  int os_tag = (int)mi_option_get(mi_option_os_tag);\n  if (os_tag < 100 || os_tag > 255) { os_tag = 254; }\n  return VM_MAKE_TAG(os_tag);\n  #else\n  return -1;\n  #endif\n}\n\nstatic void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) {\n  #if !defined(MAP_ANONYMOUS)\n  #define MAP_ANONYMOUS  MAP_ANON\n  #endif\n  #if !defined(MAP_NORESERVE)\n  #define MAP_NORESERVE  0\n  #endif\n  void* p = NULL;\n  const int fd = unix_mmap_fd();\n  int flags = MAP_PRIVATE | MAP_ANONYMOUS;\n  if (_mi_os_has_overcommit()) {\n    flags |= MAP_NORESERVE;\n  }\n  #if defined(PROT_MAX)\n  protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD\n  #endif\n  // huge page allocation\n  if (allow_large && (large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages)))) {\n    static _Atomic(size_t) large_page_try_ok; // = 0;\n    size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok);\n    if (!large_only && try_ok > 0) {\n      // If the OS is not configured for large OS pages, or the user does not have\n      // enough permission, the `mmap` will always fail (but it might also fail for other reasons).\n      // Therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times\n      // to avoid too many failing calls to mmap.\n      mi_atomic_cas_strong_acq_rel(&large_page_try_ok, &try_ok, try_ok - 1);\n    }\n    else {\n      int lflags = flags & ~MAP_NORESERVE;  // using NORESERVE on huge pages seems to fail on Linux\n      int lfd = fd;\n      #ifdef MAP_ALIGNED_SUPER\n      lflags |= MAP_ALIGNED_SUPER;\n      #endif\n      #ifdef MAP_HUGETLB\n      lflags |= MAP_HUGETLB;\n      #endif\n      #ifdef MAP_HUGE_1GB\n      static bool mi_huge_pages_available = true;\n      if (large_only && (size % MI_GiB) == 0 && mi_huge_pages_available) {\n        lflags |= MAP_HUGE_1GB;\n      }\n      else\n      #endif\n      {\n        #ifdef MAP_HUGE_2MB\n        lflags |= MAP_HUGE_2MB;\n        #endif\n      }\n      #ifdef VM_FLAGS_SUPERPAGE_SIZE_2MB\n      lfd |= VM_FLAGS_SUPERPAGE_SIZE_2MB;\n      #endif\n      if (large_only || lflags != flags) {\n        // try large OS page allocation\n        *is_large = true;\n        p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, lflags, lfd);\n        #ifdef MAP_HUGE_1GB\n        if (p == NULL && (lflags & MAP_HUGE_1GB) == MAP_HUGE_1GB) {\n          mi_huge_pages_available = false; // don't try huge 1GiB pages again\n          if (large_only) {\n            _mi_warning_message(\"unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (errno: %i)\\n\", errno);\n          }\n          lflags = ((lflags & ~MAP_HUGE_1GB) | MAP_HUGE_2MB);\n          p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, lflags, lfd);\n        }\n        #endif\n        if (large_only) return p;\n        if (p == NULL) {\n          mi_atomic_store_release(&large_page_try_ok, (size_t)8);  // on error, don't try again for the next N allocations\n        }\n      }\n    }\n  }\n  // regular allocation\n  if (p == NULL) {\n    *is_large = false;\n    p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, flags, fd);\n    #if !defined(MI_NO_THP)\n    if (p != NULL && allow_large && mi_option_is_enabled(mi_option_allow_thp) && _mi_os_canuse_large_page(size, try_alignment)) {\n      #if defined(MADV_HUGEPAGE)\n      // Many Linux systems don't allow MAP_HUGETLB but they support instead\n      // transparent huge pages (THP). Generally, it is not required to call `madvise` with MADV_HUGE\n      // though since properly aligned allocations will already use large pages if available\n      // in that case -- in particular for our large regions (in `memory.c`).\n      // However, some systems only allow THP if called with explicit `madvise`, so\n      // when large OS pages are enabled for mimalloc, we call `madvise` anyways.\n      if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {\n        // *is_large = true; // possibly\n      };\n      #elif defined(__sun)\n      struct memcntl_mha cmd = {0};\n      cmd.mha_pagesize = _mi_os_large_page_size();\n      cmd.mha_cmd = MHA_MAPSIZE_VA;\n      if (memcntl((caddr_t)p, size, MC_HAT_ADVISE, (caddr_t)&cmd, 0, 0) == 0) {\n        // *is_large = true; // possibly\n      }\n      #endif\n    }\n    #endif\n  }\n  return p;\n}\n\n// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.\nint _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {\n  mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);\n  mi_assert_internal(commit || !allow_large);\n  mi_assert_internal(try_alignment > 0);\n  if (hint_addr == NULL && size >= 8*MI_UNIX_LARGE_PAGE_SIZE && try_alignment > 1 && _mi_is_power_of_two(try_alignment) && try_alignment < MI_UNIX_LARGE_PAGE_SIZE) {\n    try_alignment = MI_UNIX_LARGE_PAGE_SIZE; // try to align along large page size for larger allocations\n  }\n\n  *is_zero = true;\n  int protect_flags = (commit ? (PROT_WRITE | PROT_READ) : PROT_NONE);\n  *addr = unix_mmap(hint_addr, size, try_alignment, protect_flags, false, allow_large, is_large);\n  return (*addr != NULL ? 0 : errno);\n}\n\n\n//---------------------------------------------\n// Commit/Reset\n//---------------------------------------------\n\nstatic void unix_mprotect_hint(int err) {\n  #if defined(__linux__) && (MI_SECURE>=2) // guard page around every mimalloc page\n  if (err == ENOMEM) {\n    _mi_warning_message(\"The next warning may be caused by a low memory map limit.\\n\"\n                        \"  On Linux this is controlled by the vm.max_map_count -- maybe increase it?\\n\"\n                        \"  For example: sudo sysctl -w vm.max_map_count=262144\\n\");\n  }\n  #else\n  MI_UNUSED(err);\n  #endif\n}\n\nint _mi_prim_commit(void* start, size_t size, bool* is_zero) {\n  // commit: ensure we can access the area\n  // note: we may think that *is_zero can be true since the memory\n  // was either from mmap PROT_NONE, or from decommit MADV_DONTNEED, but\n  // we sometimes call commit on a range with still partially committed\n  // memory and `mprotect` does not zero the range.\n  *is_zero = false;\n  int err = mprotect(start, size, (PROT_READ | PROT_WRITE));\n  if (err != 0) {\n    err = errno;\n    unix_mprotect_hint(err);\n  }\n  return err;\n}\n\nint _mi_prim_reuse(void* start, size_t size) {\n  MI_UNUSED(start); MI_UNUSED(size);\n  #if defined(__APPLE__) && defined(MADV_FREE_REUSE)\n  return unix_madvise(start, size, MADV_FREE_REUSE);\n  #endif\n  return 0;\n}\n\nint _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {\n  int err = 0;\n  #if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)\n    // decommit on macOS: use MADV_FREE_REUSABLE as it does immediate rss accounting (issue #1097)\n    err = unix_madvise(start, size, MADV_FREE_REUSABLE);\n    if (err) { err = unix_madvise(start, size, MADV_DONTNEED); }\n  #else\n    // decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)\n    err = unix_madvise(start, size, MADV_DONTNEED);\n  #endif\n  #if !MI_DEBUG && MI_SECURE<=2\n    *needs_recommit = false;\n  #else\n    *needs_recommit = true;\n    mprotect(start, size, PROT_NONE);\n  #endif\n  /*\n  // decommit: use mmap with MAP_FIXED and PROT_NONE to discard the existing memory (and reduce rss)\n  *needs_recommit = true;\n  const int fd = unix_mmap_fd();\n  void* p = mmap(start, size, PROT_NONE, (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE), fd, 0);\n  if (p != start) { err = errno; }\n  */\n  return err;\n}\n\nint _mi_prim_reset(void* start, size_t size) {\n  int err = 0;\n\n  // on macOS can use MADV_FREE_REUSABLE (but we disable this for now as it seems slower)\n  #if 0 && defined(__APPLE__) && defined(MADV_FREE_REUSABLE)\n  err = unix_madvise(start, size, MADV_FREE_REUSABLE);\n  if (err==0) return 0;\n  // fall through\n  #endif\n\n  #if defined(MADV_FREE)\n  // Otherwise, we try to use `MADV_FREE` as that is the fastest. A drawback though is that it\n  // will not reduce the `rss` stats in tools like `top` even though the memory is available\n  // to other processes. With the default `MIMALLOC_PURGE_DECOMMITS=1` we ensure that by\n  // default `MADV_DONTNEED` is used though.\n  static _Atomic(size_t) advice = MI_ATOMIC_VAR_INIT(MADV_FREE);\n  int oadvice = (int)mi_atomic_load_relaxed(&advice);\n  while ((err = unix_madvise(start, size, oadvice)) != 0 && errno == EAGAIN) { errno = 0;  };\n  if (err != 0 && errno == EINVAL && oadvice == MADV_FREE) {\n    // if MADV_FREE is not supported, fall back to MADV_DONTNEED from now on\n    mi_atomic_store_release(&advice, (size_t)MADV_DONTNEED);\n    err = unix_madvise(start, size, MADV_DONTNEED);\n  }\n  #else\n  err = unix_madvise(start, size, MADV_DONTNEED);\n  #endif\n  return err;\n}\n\nint _mi_prim_protect(void* start, size_t size, bool protect) {\n  int err = mprotect(start, size, protect ? PROT_NONE : (PROT_READ | PROT_WRITE));\n  if (err != 0) { err = errno; }\n  unix_mprotect_hint(err);\n  return err;\n}\n\n\n\n//---------------------------------------------\n// Huge page allocation\n//---------------------------------------------\n\n#if (MI_INTPTR_SIZE >= 8) && !defined(__HAIKU__) && !defined(__CYGWIN__)\n\n#ifndef MPOL_PREFERRED\n#define MPOL_PREFERRED 1\n#endif\n\n#if defined(MI_HAS_SYSCALL_H) && defined(SYS_mbind)\nstatic long mi_prim_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) {\n  return syscall(SYS_mbind, start, len, mode, nmask, maxnode, flags);\n}\n#else\nstatic long mi_prim_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) {\n  MI_UNUSED(start); MI_UNUSED(len); MI_UNUSED(mode); MI_UNUSED(nmask); MI_UNUSED(maxnode); MI_UNUSED(flags);\n  return 0;\n}\n#endif\n\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {\n  bool is_large = true;\n  *is_zero = true;\n  *addr = unix_mmap(hint_addr, size, MI_SEGMENT_SIZE, PROT_READ | PROT_WRITE, true, true, &is_large);\n  if (*addr != NULL && numa_node >= 0 && numa_node < 8*MI_INTPTR_SIZE) { // at most 64 nodes\n    unsigned long numa_mask = (1UL << numa_node);\n    // TODO: does `mbind` work correctly for huge OS pages? should we\n    // use `set_mempolicy` before calling mmap instead?\n    // see: <https://lkml.org/lkml/2017/2/9/875>\n    long err = mi_prim_mbind(*addr, size, MPOL_PREFERRED, &numa_mask, 8*MI_INTPTR_SIZE, 0);\n    if (err != 0) {\n      err = errno;\n      _mi_warning_message(\"failed to bind huge (1GiB) pages to numa node %d (error: %d (0x%x))\\n\", numa_node, err, err);\n    }\n  }\n  return (*addr != NULL ? 0 : errno);\n}\n\n#else\n\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {\n  MI_UNUSED(hint_addr); MI_UNUSED(size); MI_UNUSED(numa_node);\n  *is_zero = false;\n  *addr = NULL;\n  return ENOMEM;\n}\n\n#endif\n\n//---------------------------------------------\n// NUMA nodes\n//---------------------------------------------\n\n#if defined(__linux__)\n\nsize_t _mi_prim_numa_node(void) {\n  #if defined(MI_HAS_SYSCALL_H) && defined(SYS_getcpu)\n    unsigned long node = 0;\n    unsigned long ncpu = 0;\n    long err = syscall(SYS_getcpu, &ncpu, &node, NULL);\n    if (err != 0) return 0;\n    return node;\n  #else\n    return 0;\n  #endif\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  char buf[128];\n  unsigned node = 0;\n  for(node = 0; node < 256; node++) {\n    // enumerate node entries -- todo: it there a more efficient way to do this? (but ensure there is no allocation)\n    _mi_snprintf(buf, 127, \"/sys/devices/system/node/node%u\", node + 1);\n    if (mi_prim_access(buf,R_OK) != 0) break;\n  }\n  return (node+1);\n}\n\n#elif defined(__FreeBSD__) && __FreeBSD_version >= 1200000\n\nsize_t _mi_prim_numa_node(void) {\n  domainset_t dom;\n  size_t node;\n  int policy;\n  if (cpuset_getdomain(CPU_LEVEL_CPUSET, CPU_WHICH_PID, -1, sizeof(dom), &dom, &policy) == -1) return 0ul;\n  for (node = 0; node < MAXMEMDOM; node++) {\n    if (DOMAINSET_ISSET(node, &dom)) return node;\n  }\n  return 0ul;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  size_t ndomains = 0;\n  size_t len = sizeof(ndomains);\n  if (sysctlbyname(\"vm.ndomains\", &ndomains, &len, NULL, 0) == -1) return 0ul;\n  return ndomains;\n}\n\n#elif defined(__DragonFly__)\n\nsize_t _mi_prim_numa_node(void) {\n  // TODO: DragonFly does not seem to provide any userland means to get this information.\n  return 0ul;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  size_t ncpus = 0, nvirtcoresperphys = 0;\n  size_t len = sizeof(size_t);\n  if (sysctlbyname(\"hw.ncpu\", &ncpus, &len, NULL, 0) == -1) return 0ul;\n  if (sysctlbyname(\"hw.cpu_topology_ht_ids\", &nvirtcoresperphys, &len, NULL, 0) == -1) return 0ul;\n  return nvirtcoresperphys * ncpus;\n}\n\n#else\n\nsize_t _mi_prim_numa_node(void) {\n  return 0;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  return 1;\n}\n\n#endif\n\n// ----------------------------------------------------------------\n// Clock\n// ----------------------------------------------------------------\n\n#include <time.h>\n\n#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)\n\nmi_msecs_t _mi_prim_clock_now(void) {\n  struct timespec t;\n  #ifdef CLOCK_MONOTONIC\n  clock_gettime(CLOCK_MONOTONIC, &t);\n  #else\n  clock_gettime(CLOCK_REALTIME, &t);\n  #endif\n  return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);\n}\n\n#else\n\n// low resolution timer\nmi_msecs_t _mi_prim_clock_now(void) {\n  #if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)\n  return (mi_msecs_t)clock();\n  #elif (CLOCKS_PER_SEC < 1000)\n  return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);\n  #else\n  return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);\n  #endif\n}\n\n#endif\n\n\n\n\n//----------------------------------------------------------------\n// Process info\n//----------------------------------------------------------------\n\n#if defined(__unix__) || defined(__unix) || defined(unix) || defined(__APPLE__) || defined(__HAIKU__)\n#include <stdio.h>\n#include <unistd.h>\n#include <sys/resource.h>\n\n#if defined(__APPLE__)\n#include <mach/mach.h>\n#endif\n\n#if defined(__HAIKU__)\n#include <kernel/OS.h>\n#endif\n\nstatic mi_msecs_t timeval_secs(const struct timeval* tv) {\n  return ((mi_msecs_t)tv->tv_sec * 1000L) + ((mi_msecs_t)tv->tv_usec / 1000L);\n}\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo)\n{\n  struct rusage rusage;\n  getrusage(RUSAGE_SELF, &rusage);\n  pinfo->utime = timeval_secs(&rusage.ru_utime);\n  pinfo->stime = timeval_secs(&rusage.ru_stime);\n#if !defined(__HAIKU__)\n  pinfo->page_faults = rusage.ru_majflt;\n#endif\n#if defined(__HAIKU__)\n  // Haiku does not have (yet?) a way to\n  // get these stats per process\n  thread_info tid;\n  area_info mem;\n  ssize_t c;\n  get_thread_info(find_thread(0), &tid);\n  while (get_next_area_info(tid.team, &c, &mem) == B_OK) {\n    pinfo->peak_rss += mem.ram_size;\n  }\n  pinfo->page_faults = 0;\n#elif defined(__APPLE__)\n  pinfo->peak_rss = rusage.ru_maxrss;         // macos reports in bytes\n  #ifdef MACH_TASK_BASIC_INFO\n  struct mach_task_basic_info info;\n  mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;\n  if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {\n    pinfo->current_rss = (size_t)info.resident_size;\n  }\n  #else\n  struct task_basic_info info;\n  mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;\n  if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {\n    pinfo->current_rss = (size_t)info.resident_size;\n  }\n  #endif\n#else\n  pinfo->peak_rss = rusage.ru_maxrss * 1024;  // Linux/BSD report in KiB\n#endif\n  // use defaults for commit\n}\n\n#else\n\n#ifndef __wasi__\n// WebAssembly instances are not processes\n#pragma message(\"define a way to get process info\")\n#endif\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo)\n{\n  // use defaults\n  MI_UNUSED(pinfo);\n}\n\n#endif\n\n\n//----------------------------------------------------------------\n// Output\n//----------------------------------------------------------------\n\nvoid _mi_prim_out_stderr( const char* msg ) {\n  fputs(msg,stderr);\n}\n\n\n//----------------------------------------------------------------\n// Environment\n//----------------------------------------------------------------\n\n#if !defined(MI_USE_ENVIRON) || (MI_USE_ENVIRON!=0)\n// On Posix systemsr use `environ` to access environment variables\n// even before the C runtime is initialized.\n#if defined(__APPLE__) && defined(__has_include) && __has_include(<crt_externs.h>)\n#include <crt_externs.h>\nstatic char** mi_get_environ(void) {\n  return (*_NSGetEnviron());\n}\n#else\nextern char** environ;\nstatic char** mi_get_environ(void) {\n  return environ;\n}\n#endif\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size) {\n  if (name==NULL) return false;\n  const size_t len = _mi_strlen(name);\n  if (len == 0) return false;\n  char** env = mi_get_environ();\n  if (env == NULL) return false;\n  // compare up to 10000 entries\n  for (int i = 0; i < 10000 && env[i] != NULL; i++) {\n    const char* s = env[i];\n    if (_mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive\n      // found it\n      _mi_strlcpy(result, s + len + 1, result_size);\n      return true;\n    }\n  }\n  return false;\n}\n#else\n// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size) {\n  // cannot call getenv() when still initializing the C runtime.\n  if (_mi_preloading()) return false;\n  const char* s = getenv(name);\n  if (s == NULL) {\n    // we check the upper case name too.\n    char buf[64+1];\n    size_t len = _mi_strnlen(name,sizeof(buf)-1);\n    for (size_t i = 0; i < len; i++) {\n      buf[i] = _mi_toupper(name[i]);\n    }\n    buf[len] = 0;\n    s = getenv(buf);\n  }\n  if (s == NULL || _mi_strnlen(s,result_size) >= result_size)  return false;\n  _mi_strlcpy(result, s, result_size);\n  return true;\n}\n#endif  // !MI_USE_ENVIRON\n\n\n//----------------------------------------------------------------\n// Random\n//----------------------------------------------------------------\n\n#if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_15) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15)\n#include <CommonCrypto/CommonCryptoError.h>\n#include <CommonCrypto/CommonRandom.h>\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  // We prefer CCRandomGenerateBytes as it returns an error code while arc4random_buf\n  // may fail silently on macOS. See PR #390, and <https://opensource.apple.com/source/Libc/Libc-1439.40.11/gen/FreeBSD/arc4random.c.auto.html>\n  return (CCRandomGenerateBytes(buf, buf_len) == kCCSuccess);\n}\n\n#elif defined(__ANDROID__) || defined(__DragonFly__) || \\\n      defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \\\n      defined(__sun) || \\\n      (defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7))\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  arc4random_buf(buf, buf_len);\n  return true;\n}\n\n#elif defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__)   // also for old apple versions < 10.7 (issue #829)\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <errno.h>\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  // Modern Linux provides `getrandom` but different distributions either use `sys/random.h` or `linux/random.h`\n  // and for the latter the actual `getrandom` call is not always defined.\n  // (see <https://stackoverflow.com/questions/45237324/why-doesnt-getrandom-compile>)\n  // We therefore use a syscall directly and fall back dynamically to /dev/urandom when needed.\n  #if defined(MI_HAS_SYSCALL_H) && defined(SYS_getrandom)\n    #ifndef GRND_NONBLOCK\n    #define GRND_NONBLOCK (1)\n    #endif\n    static _Atomic(uintptr_t) no_getrandom; // = 0\n    if (mi_atomic_load_acquire(&no_getrandom)==0) {\n      ssize_t ret = syscall(SYS_getrandom, buf, buf_len, GRND_NONBLOCK);\n      if (ret >= 0) return (buf_len == (size_t)ret);\n      if (errno != ENOSYS) return false;\n      mi_atomic_store_release(&no_getrandom, (uintptr_t)1); // don't call again, and fall back to /dev/urandom\n    }\n  #endif\n  int flags = O_RDONLY;\n  #if defined(O_CLOEXEC)\n  flags |= O_CLOEXEC;\n  #endif\n  int fd = mi_prim_open(\"/dev/urandom\", flags);\n  if (fd < 0) return false;\n  size_t count = 0;\n  while(count < buf_len) {\n    ssize_t ret = mi_prim_read(fd, (char*)buf + count, buf_len - count);\n    if (ret<=0) {\n      if (errno!=EAGAIN && errno!=EINTR) break;\n    }\n    else {\n      count += ret;\n    }\n  }\n  mi_prim_close(fd);\n  return (count==buf_len);\n}\n\n#else\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  return false;\n}\n\n#endif\n\n\n//----------------------------------------------------------------\n// Thread init/done\n//----------------------------------------------------------------\n\n#if defined(MI_USE_PTHREADS)\n\n// use pthread local storage keys to detect thread ending\n// (and used with MI_TLS_PTHREADS for the default heap)\npthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);\n\nstatic void mi_pthread_done(void* value) {\n  if (value!=NULL) {\n    _mi_thread_done((mi_heap_t*)value);\n  }\n}\n\nvoid _mi_prim_thread_init_auto_done(void) {\n  mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));\n  pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);\n}\n\nvoid _mi_prim_thread_done_auto_done(void) {\n  if (_mi_heap_default_key != (pthread_key_t)(-1)) {  // do not leak the key, see issue #809\n    pthread_key_delete(_mi_heap_default_key);\n  }\n}\n\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n  if (_mi_heap_default_key != (pthread_key_t)(-1)) {  // can happen during recursive invocation on freeBSD\n    pthread_setspecific(_mi_heap_default_key, heap);\n  }\n}\n\n#else\n\nvoid _mi_prim_thread_init_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_done_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n  MI_UNUSED(heap);\n}\n\n#endif\n"
  },
  {
    "path": "src/prim/wasi/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// This file is included in `src/prim/prim.c`\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"\n\n#include <stdio.h>   // fputs\n#include <stdlib.h>  // getenv\n\n//---------------------------------------------\n// Initialize\n//---------------------------------------------\n\nvoid _mi_prim_mem_init( mi_os_mem_config_t* config ) {\n  config->page_size = 64*MI_KiB; // WebAssembly has a fixed page size: 64KiB\n  config->alloc_granularity = 16;\n  config->has_overcommit = false;\n  config->has_partial_free = false;\n  config->has_virtual_reserve = false;\n}\n\n//---------------------------------------------\n// Free\n//---------------------------------------------\n\nint _mi_prim_free(void* addr, size_t size ) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  // wasi heap cannot be shrunk\n  return 0;\n}\n\n\n//---------------------------------------------\n// Allocation: sbrk or memory_grow\n//---------------------------------------------\n\n#if defined(MI_USE_SBRK)\n  #include <unistd.h>  // for sbrk\n\n  static void* mi_memory_grow( size_t size ) {\n    void* p = sbrk(size);\n    if (p == (void*)(-1)) return NULL;\n    #if !defined(__wasi__) // on wasi this is always zero initialized already (?)\n    memset(p,0,size);\n    #endif\n    return p;\n  }\n#elif defined(__wasi__)\n  static void* mi_memory_grow( size_t size ) {\n    size_t base = (size > 0 ? __builtin_wasm_memory_grow(0,_mi_divide_up(size, _mi_os_page_size()))\n                            : __builtin_wasm_memory_size(0));\n    if (base == SIZE_MAX) return NULL;\n    return (void*)(base * _mi_os_page_size());\n  }\n#endif\n\n#if defined(MI_USE_PTHREADS)\nstatic pthread_mutex_t mi_heap_grow_mutex = PTHREAD_MUTEX_INITIALIZER;\n#endif\n\nstatic void* mi_prim_mem_grow(size_t size, size_t try_alignment) {\n  void* p = NULL;\n  if (try_alignment <= 1) {\n    // `sbrk` is not thread safe in general so try to protect it (we could skip this on WASM but leave it in for now)\n    #if defined(MI_USE_PTHREADS)\n    pthread_mutex_lock(&mi_heap_grow_mutex);\n    #endif\n    p = mi_memory_grow(size);\n    #if defined(MI_USE_PTHREADS)\n    pthread_mutex_unlock(&mi_heap_grow_mutex);\n    #endif\n  }\n  else {\n    void* base = NULL;\n    size_t alloc_size = 0;\n    // to allocate aligned use a lock to try to avoid thread interaction\n    // between getting the current size and actual allocation\n    // (also, `sbrk` is not thread safe in general)\n    #if defined(MI_USE_PTHREADS)\n    pthread_mutex_lock(&mi_heap_grow_mutex);\n    #endif\n    {\n      void* current = mi_memory_grow(0);  // get current size\n      if (current != NULL) {\n        void* aligned_current = mi_align_up_ptr(current, try_alignment);  // and align from there to minimize wasted space\n        alloc_size = _mi_align_up( ((uint8_t*)aligned_current - (uint8_t*)current) + size, _mi_os_page_size());\n        base = mi_memory_grow(alloc_size);\n      }\n    }\n    #if defined(MI_USE_PTHREADS)\n    pthread_mutex_unlock(&mi_heap_grow_mutex);\n    #endif\n    if (base != NULL) {\n      p = mi_align_up_ptr(base, try_alignment);\n      if ((uint8_t*)p + size > (uint8_t*)base + alloc_size) {\n        // another thread used wasm_memory_grow/sbrk in-between and we do not have enough\n        // space after alignment. Give up (and waste the space as we cannot shrink :-( )\n        // (in `mi_os_mem_alloc_aligned` this will fall back to overallocation to align)\n        p = NULL;\n      }\n    }\n  }\n  /*\n  if (p == NULL) {\n    _mi_warning_message(\"unable to allocate sbrk/wasm_memory_grow OS memory (%zu bytes, %zu alignment)\\n\", size, try_alignment);\n    errno = ENOMEM;\n    return NULL;\n  }\n  */\n  mi_assert_internal( p == NULL || try_alignment == 0 || (uintptr_t)p % try_alignment == 0 );\n  return p;\n}\n\n// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.\nint _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {\n  MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr);\n  *is_large = false;\n  *is_zero = false;\n  *addr = mi_prim_mem_grow(size, try_alignment);\n  return (*addr != NULL ? 0 : ENOMEM);\n}\n\n\n//---------------------------------------------\n// Commit/Reset/Protect\n//---------------------------------------------\n\nint _mi_prim_commit(void* addr, size_t size, bool* is_zero) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  *is_zero = false;\n  return 0;\n}\n\nint _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  *needs_recommit = false;\n  return 0;\n}\n\nint _mi_prim_reset(void* addr, size_t size) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  return 0;\n}\n\nint _mi_prim_reuse(void* addr, size_t size) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  return 0;\n}\n\nint _mi_prim_protect(void* addr, size_t size, bool protect) {\n  MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);\n  return 0;\n}\n\n\n//---------------------------------------------\n// Huge pages and NUMA nodes\n//---------------------------------------------\n\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {\n  MI_UNUSED(hint_addr); MI_UNUSED(size); MI_UNUSED(numa_node);\n  *is_zero = true;\n  *addr = NULL;\n  return ENOSYS;\n}\n\nsize_t _mi_prim_numa_node(void) {\n  return 0;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  return 1;\n}\n\n\n//----------------------------------------------------------------\n// Clock\n//----------------------------------------------------------------\n\n#include <time.h>\n\n#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)\n\nmi_msecs_t _mi_prim_clock_now(void) {\n  struct timespec t;\n  #ifdef CLOCK_MONOTONIC\n  clock_gettime(CLOCK_MONOTONIC, &t);\n  #else\n  clock_gettime(CLOCK_REALTIME, &t);\n  #endif\n  return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);\n}\n\n#else\n\n// low resolution timer\nmi_msecs_t _mi_prim_clock_now(void) {\n  #if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)\n  return (mi_msecs_t)clock();\n  #elif (CLOCKS_PER_SEC < 1000)\n  return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);\n  #else\n  return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);\n  #endif\n}\n\n#endif\n\n\n//----------------------------------------------------------------\n// Process info\n//----------------------------------------------------------------\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo)\n{\n  // use defaults\n  MI_UNUSED(pinfo);\n}\n\n\n//----------------------------------------------------------------\n// Output\n//----------------------------------------------------------------\n\nvoid _mi_prim_out_stderr( const char* msg ) {\n  fputs(msg,stderr);\n}\n\n\n//----------------------------------------------------------------\n// Environment\n//----------------------------------------------------------------\n\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size) {\n  // cannot call getenv() when still initializing the C runtime.\n  if (_mi_preloading()) return false;\n  const char* s = getenv(name);\n  if (s == NULL) {\n    // we check the upper case name too.\n    char buf[64+1];\n    size_t len = _mi_strnlen(name,sizeof(buf)-1);\n    for (size_t i = 0; i < len; i++) {\n      buf[i] = _mi_toupper(name[i]);\n    }\n    buf[len] = 0;\n    s = getenv(buf);\n  }\n  if (s == NULL || _mi_strnlen(s,result_size) >= result_size)  return false;\n  _mi_strlcpy(result, s, result_size);\n  return true;\n}\n\n\n//----------------------------------------------------------------\n// Random\n//----------------------------------------------------------------\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  return false;\n}\n\n\n//----------------------------------------------------------------\n// Thread init/done\n//----------------------------------------------------------------\n\nvoid _mi_prim_thread_init_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_done_auto_done(void) {\n  // nothing\n}\n\nvoid _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n  MI_UNUSED(heap);\n}\n"
  },
  {
    "path": "src/prim/windows/etw-mimalloc.wprp",
    "content": "<WindowsPerformanceRecorder Version=\"1.0\">\n  <Profiles>\n    <SystemCollector Id=\"WPR_initiated_WprApp_WPR_System_Collector\" Name=\"WPR_initiated_WprApp_WPR System Collector\">\n      <BufferSize Value=\"1024\" />\n      <Buffers Value=\"100\" />\n    </SystemCollector>\n    <EventCollector Id=\"Mimalloc_Collector\" Name=\"Mimalloc Collector\">\n      <BufferSize Value=\"1024\" />\n      <Buffers Value=\"100\" />\n    </EventCollector>\n    <SystemProvider Id=\"WPR_initiated_WprApp_WPR_System_Collector_Provider\">\n      <Keywords>\n        <Keyword Value=\"Loader\" />\n      </Keywords>\n    </SystemProvider>\n    <EventProvider Id=\"MimallocEventProvider\" Name=\"138f4dbb-ee04-4899-aa0a-572ad4475779\" NonPagedMemory=\"true\" Stack=\"true\">\n      <EventFilters FilterIn=\"true\">\n        <EventId Value=\"100\" />\n        <EventId Value=\"101\" />\n      </EventFilters>\n    </EventProvider>\n    <Profile Id=\"CustomHeap.Verbose.File\" Name=\"CustomHeap\" Description=\"RunningProfile:CustomHeap.Verbose.File\" LoggingMode=\"File\" DetailLevel=\"Verbose\">\n      <ProblemCategories>\n        <ProblemCategory Value=\"Resource Analysis\" />\n      </ProblemCategories>\n      <Collectors>\n        <SystemCollectorId Value=\"WPR_initiated_WprApp_WPR_System_Collector\">\n          <SystemProviderId Value=\"WPR_initiated_WprApp_WPR_System_Collector_Provider\" />\n        </SystemCollectorId>\n        <EventCollectorId Value=\"Mimalloc_Collector\">\n          <EventProviders>\n            <EventProviderId Value=\"MimallocEventProvider\" >\n              <Keywords>\n                <Keyword Value=\"100\"/>\n                <Keyword Value=\"101\"/>\n              </Keywords>\n            </EventProviderId>\n          </EventProviders>\n        </EventCollectorId>\n      </Collectors>\n      <TraceMergeProperties>\n        <TraceMergeProperty Id=\"BaseVerboseTraceMergeProperties\" Name=\"BaseTraceMergeProperties\">\n          <DeletePreMergedTraceFiles Value=\"true\" />\n          <FileCompression Value=\"false\" />\n          <InjectOnly Value=\"false\" />\n          <SkipMerge Value=\"false\" />\n          <CustomEvents>\n            <CustomEvent Value=\"ImageId\" />\n            <CustomEvent Value=\"BuildInfo\" />\n            <CustomEvent Value=\"VolumeMapping\" />\n            <CustomEvent Value=\"EventMetadata\" />\n            <CustomEvent Value=\"PerfTrackMetadata\" />\n            <CustomEvent Value=\"WinSAT\" />\n            <CustomEvent Value=\"NetworkInterface\" />\n          </CustomEvents>\n        </TraceMergeProperty>\n      </TraceMergeProperties>\n    </Profile>\n  </Profiles>\n</WindowsPerformanceRecorder>\n\n"
  },
  {
    "path": "src/prim/windows/etw.h",
    "content": "//**********************************************************************`\n//* This is an include file generated by Message Compiler.             *`\n//*                                                                    *`\n//* Copyright (c) Microsoft Corporation. All Rights Reserved.          *`\n//**********************************************************************`\n#pragma once\n\n//*****************************************************************************\n//\n// Notes on the ETW event code generated by MC:\n//\n// - Structures and arrays of structures are treated as an opaque binary blob.\n//   The caller is responsible for packing the data for the structure into a\n//   single region of memory, with no padding between values. The macro will\n//   have an extra parameter for the length of the blob.\n// - Arrays of nul-terminated strings must be packed by the caller into a\n//   single binary blob containing the correct number of strings, with a nul\n//   after each string. The size of the blob is specified in characters, and\n//   includes the final nul.\n// - Arrays of SID are treated as a single binary blob. The caller is\n//   responsible for packing the SID values into a single region of memory with\n//   no padding.\n// - The length attribute on the data element in the manifest is significant\n//   for values with intype win:UnicodeString, win:AnsiString, or win:Binary.\n//   The length attribute must be specified for win:Binary, and is optional for\n//   win:UnicodeString and win:AnsiString (if no length is given, the strings\n//   are assumed to be nul-terminated). For win:UnicodeString, the length is\n//   measured in characters, not bytes.\n// - For an array of win:UnicodeString, win:AnsiString, or win:Binary, the\n//   length attribute applies to every value in the array, so every value in\n//   the array must have the same length. The values in the array are provided\n//   to the macro via a single pointer -- the caller is responsible for packing\n//   all of the values into a single region of memory with no padding between\n//   values.\n// - Values of type win:CountedUnicodeString, win:CountedAnsiString, and\n//   win:CountedBinary can be generated and collected on Vista or later.\n//   However, they may not decode properly without the Windows 10 2018 Fall\n//   Update.\n// - Arrays of type win:CountedUnicodeString, win:CountedAnsiString, and\n//   win:CountedBinary must be packed by the caller into a single region of\n//   memory. The format for each item is a UINT16 byte-count followed by that\n//   many bytes of data. When providing the array to the generated macro, you\n//   must provide the total size of the packed array data, including the UINT16\n//   sizes for each item. In the case of win:CountedUnicodeString, the data\n//   size is specified in WCHAR (16-bit) units. In the case of\n//   win:CountedAnsiString and win:CountedBinary, the data size is specified in\n//   bytes.\n//\n//*****************************************************************************\n\n#include <wmistr.h>\n#include <evntrace.h>\n#include <evntprov.h>\n\n#ifndef ETW_INLINE\n  #ifdef _ETW_KM_\n    // In kernel mode, save stack space by never inlining templates.\n    #define ETW_INLINE DECLSPEC_NOINLINE __inline\n  #else\n    // In user mode, save code size by inlining templates as appropriate.\n    #define ETW_INLINE __inline\n  #endif\n#endif // ETW_INLINE\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n//\n// MCGEN_DISABLE_PROVIDER_CODE_GENERATION macro:\n// Define this macro to have the compiler skip the generated functions in this\n// header.\n//\n#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n//\n// MCGEN_USE_KERNEL_MODE_APIS macro:\n// Controls whether the generated code uses kernel-mode or user-mode APIs.\n// - Set to 0 to use Windows user-mode APIs such as EventRegister.\n// - Set to 1 to use Windows kernel-mode APIs such as EtwRegister.\n// Default is based on whether the _ETW_KM_ macro is defined (i.e. by wdm.h).\n// Note that the APIs can also be overridden directly, e.g. by setting the\n// MCGEN_EVENTWRITETRANSFER or MCGEN_EVENTREGISTER macros.\n//\n#ifndef MCGEN_USE_KERNEL_MODE_APIS\n  #ifdef _ETW_KM_\n    #define MCGEN_USE_KERNEL_MODE_APIS 1\n  #else\n    #define MCGEN_USE_KERNEL_MODE_APIS 0\n  #endif\n#endif // MCGEN_USE_KERNEL_MODE_APIS\n\n//\n// MCGEN_HAVE_EVENTSETINFORMATION macro:\n// Controls how McGenEventSetInformation uses the EventSetInformation API.\n// - Set to 0 to disable the use of EventSetInformation\n//   (McGenEventSetInformation will always return an error).\n// - Set to 1 to directly invoke MCGEN_EVENTSETINFORMATION.\n// - Set to 2 to to locate EventSetInformation at runtime via GetProcAddress\n//   (user-mode) or MmGetSystemRoutineAddress (kernel-mode).\n// Default is determined as follows:\n// - If MCGEN_EVENTSETINFORMATION has been customized, set to 1\n//   (i.e. use MCGEN_EVENTSETINFORMATION).\n// - Else if the target OS version has EventSetInformation, set to 1\n//   (i.e. use MCGEN_EVENTSETINFORMATION).\n// - Else set to 2 (i.e. try to dynamically locate EventSetInformation).\n// Note that an McGenEventSetInformation function will only be generated if one\n// or more provider in a manifest has provider traits.\n//\n#ifndef MCGEN_HAVE_EVENTSETINFORMATION\n  #ifdef MCGEN_EVENTSETINFORMATION             // if MCGEN_EVENTSETINFORMATION has been customized,\n    #define MCGEN_HAVE_EVENTSETINFORMATION   1 //   directly invoke MCGEN_EVENTSETINFORMATION(...).\n  #elif MCGEN_USE_KERNEL_MODE_APIS             // else if using kernel-mode APIs,\n    #if NTDDI_VERSION >= 0x06040000            //   if target OS is Windows 10 or later,\n      #define MCGEN_HAVE_EVENTSETINFORMATION 1 //     directly invoke MCGEN_EVENTSETINFORMATION(...).\n    #else                                      //   else\n      #define MCGEN_HAVE_EVENTSETINFORMATION 2 //     find \"EtwSetInformation\" via MmGetSystemRoutineAddress.\n    #endif                                     // else (using user-mode APIs)\n  #else                                        //   if target OS and SDK is Windows 8 or later,\n    #if WINVER >= 0x0602 && defined(EVENT_FILTER_TYPE_SCHEMATIZED)\n      #define MCGEN_HAVE_EVENTSETINFORMATION 1 //     directly invoke MCGEN_EVENTSETINFORMATION(...).\n    #else                                      //   else\n      #define MCGEN_HAVE_EVENTSETINFORMATION 2 //     find \"EventSetInformation\" via GetModuleHandleExW/GetProcAddress.\n    #endif\n  #endif\n#endif // MCGEN_HAVE_EVENTSETINFORMATION\n\n//\n// MCGEN Override Macros\n//\n// The following override macros may be defined before including this header\n// to control the APIs used by this header:\n//\n// - MCGEN_EVENTREGISTER\n// - MCGEN_EVENTUNREGISTER\n// - MCGEN_EVENTSETINFORMATION\n// - MCGEN_EVENTWRITETRANSFER\n//\n// If the the macro is undefined, the MC implementation will default to the\n// corresponding ETW APIs. For example, if the MCGEN_EVENTREGISTER macro is\n// undefined, the EventRegister[MyProviderName] macro will use EventRegister\n// in user mode and will use EtwRegister in kernel mode.\n//\n// To prevent issues from conflicting definitions of these macros, the value\n// of the override macro will be used as a suffix in certain internal function\n// names. Because of this, the override macros must follow certain rules:\n//\n// - The macro must be defined before any MC-generated header is included and\n//   must not be undefined or redefined after any MC-generated header is\n//   included. Different translation units (i.e. different .c or .cpp files)\n//   may set the macros to different values, but within a translation unit\n//   (within a single .c or .cpp file), the macro must be set once and not\n//   changed.\n// - The override must be an object-like macro, not a function-like macro\n//   (i.e. the override macro must not have a parameter list).\n// - The override macro's value must be a simple identifier, i.e. must be\n//   something that starts with a letter or '_' and contains only letters,\n//   numbers, and '_' characters.\n// - If the override macro's value is the name of a second object-like macro,\n//   the second object-like macro must follow the same rules. (The override\n//   macro's value can also be the name of a function-like macro, in which\n//   case the function-like macro does not need to follow the same rules.)\n//\n// For example, the following will cause compile errors:\n//\n//   #define MCGEN_EVENTWRITETRANSFER MyNamespace::MyClass::MyFunction // Value has non-identifier characters (colon).\n//   #define MCGEN_EVENTWRITETRANSFER GetEventWriteFunctionPointer(7)  // Value has non-identifier characters (parentheses).\n//   #define MCGEN_EVENTWRITETRANSFER(h,e,a,r,c,d) EventWrite(h,e,c,d) // Override is defined as a function-like macro.\n//   #define MY_OBJECT_LIKE_MACRO     MyNamespace::MyClass::MyEventWriteFunction\n//   #define MCGEN_EVENTWRITETRANSFER MY_OBJECT_LIKE_MACRO // Evaluates to something with non-identifier characters (colon).\n//\n// The following would be ok:\n//\n//   #define MCGEN_EVENTWRITETRANSFER  MyEventWriteFunction1  // OK, suffix will be \"MyEventWriteFunction1\".\n//   #define MY_OBJECT_LIKE_MACRO      MyEventWriteFunction2\n//   #define MCGEN_EVENTWRITETRANSFER  MY_OBJECT_LIKE_MACRO   // OK, suffix will be \"MyEventWriteFunction2\".\n//   #define MY_FUNCTION_LIKE_MACRO(h,e,a,r,c,d) MyNamespace::MyClass::MyEventWriteFunction3(h,e,c,d)\n//   #define MCGEN_EVENTWRITETRANSFER  MY_FUNCTION_LIKE_MACRO // OK, suffix will be \"MY_FUNCTION_LIKE_MACRO\".\n//\n#ifndef MCGEN_EVENTREGISTER\n  #if MCGEN_USE_KERNEL_MODE_APIS\n    #define MCGEN_EVENTREGISTER        EtwRegister\n  #else\n    #define MCGEN_EVENTREGISTER        EventRegister\n  #endif\n#endif // MCGEN_EVENTREGISTER\n#ifndef MCGEN_EVENTUNREGISTER\n  #if MCGEN_USE_KERNEL_MODE_APIS\n    #define MCGEN_EVENTUNREGISTER      EtwUnregister\n  #else\n    #define MCGEN_EVENTUNREGISTER      EventUnregister\n  #endif\n#endif // MCGEN_EVENTUNREGISTER\n#ifndef MCGEN_EVENTSETINFORMATION\n  #if MCGEN_USE_KERNEL_MODE_APIS\n    #define MCGEN_EVENTSETINFORMATION  EtwSetInformation\n  #else\n    #define MCGEN_EVENTSETINFORMATION  EventSetInformation\n  #endif\n#endif // MCGEN_EVENTSETINFORMATION\n#ifndef MCGEN_EVENTWRITETRANSFER\n  #if MCGEN_USE_KERNEL_MODE_APIS\n    #define MCGEN_EVENTWRITETRANSFER   EtwWriteTransfer\n  #else\n    #define MCGEN_EVENTWRITETRANSFER   EventWriteTransfer\n  #endif\n#endif // MCGEN_EVENTWRITETRANSFER\n\n//\n// MCGEN_EVENT_ENABLED macro:\n// Override to control how the EventWrite[EventName] macros determine whether\n// an event is enabled. The default behavior is for EventWrite[EventName] to\n// use the EventEnabled[EventName] macros.\n//\n#ifndef MCGEN_EVENT_ENABLED\n#define MCGEN_EVENT_ENABLED(EventName) EventEnabled##EventName()\n#endif\n\n//\n// MCGEN_EVENT_ENABLED_FORCONTEXT macro:\n// Override to control how the EventWrite[EventName]_ForContext macros\n// determine whether an event is enabled. The default behavior is for\n// EventWrite[EventName]_ForContext to use the\n// EventEnabled[EventName]_ForContext macros.\n//\n#ifndef MCGEN_EVENT_ENABLED_FORCONTEXT\n#define MCGEN_EVENT_ENABLED_FORCONTEXT(pContext, EventName) EventEnabled##EventName##_ForContext(pContext)\n#endif\n\n//\n// MCGEN_ENABLE_CHECK macro:\n// Determines whether the specified event would be considered as enabled\n// based on the state of the specified context. Slightly faster than calling\n// McGenEventEnabled directly.\n//\n#ifndef MCGEN_ENABLE_CHECK\n#define MCGEN_ENABLE_CHECK(Context, Descriptor) (Context.IsEnabled && McGenEventEnabled(&Context, &Descriptor))\n#endif\n\n#if !defined(MCGEN_TRACE_CONTEXT_DEF)\n#define MCGEN_TRACE_CONTEXT_DEF\n// This structure is for use by MC-generated code and should not be used directly.\ntypedef struct _MCGEN_TRACE_CONTEXT\n{\n    TRACEHANDLE            RegistrationHandle;\n    TRACEHANDLE            Logger;      // Used as pointer to provider traits.\n    ULONGLONG              MatchAnyKeyword;\n    ULONGLONG              MatchAllKeyword;\n    ULONG                  Flags;\n    ULONG                  IsEnabled;\n    UCHAR                  Level;\n    UCHAR                  Reserve;\n    USHORT                 EnableBitsCount;\n    PULONG                 EnableBitMask;\n    const ULONGLONG*       EnableKeyWords;\n    const UCHAR*           EnableLevel;\n} MCGEN_TRACE_CONTEXT, *PMCGEN_TRACE_CONTEXT;\n#endif // MCGEN_TRACE_CONTEXT_DEF\n\n#if !defined(MCGEN_LEVEL_KEYWORD_ENABLED_DEF)\n#define MCGEN_LEVEL_KEYWORD_ENABLED_DEF\n//\n// Determines whether an event with a given Level and Keyword would be\n// considered as enabled based on the state of the specified context.\n// Note that you may want to use MCGEN_ENABLE_CHECK instead of calling this\n// function directly.\n//\nFORCEINLINE\nBOOLEAN\nMcGenLevelKeywordEnabled(\n    _In_ PMCGEN_TRACE_CONTEXT EnableInfo,\n    _In_ UCHAR Level,\n    _In_ ULONGLONG Keyword\n    )\n{\n    //\n    // Check if the event Level is lower than the level at which\n    // the channel is enabled.\n    // If the event Level is 0 or the channel is enabled at level 0,\n    // all levels are enabled.\n    //\n\n    if ((Level <= EnableInfo->Level) || // This also covers the case of Level == 0.\n        (EnableInfo->Level == 0)) {\n\n        //\n        // Check if Keyword is enabled\n        //\n\n        if ((Keyword == (ULONGLONG)0) ||\n            ((Keyword & EnableInfo->MatchAnyKeyword) &&\n             ((Keyword & EnableInfo->MatchAllKeyword) == EnableInfo->MatchAllKeyword))) {\n            return TRUE;\n        }\n    }\n\n    return FALSE;\n}\n#endif // MCGEN_LEVEL_KEYWORD_ENABLED_DEF\n\n#if !defined(MCGEN_EVENT_ENABLED_DEF)\n#define MCGEN_EVENT_ENABLED_DEF\n//\n// Determines whether the specified event would be considered as enabled based\n// on the state of the specified context. Note that you may want to use\n// MCGEN_ENABLE_CHECK instead of calling this function directly.\n//\nFORCEINLINE\nBOOLEAN\nMcGenEventEnabled(\n    _In_ PMCGEN_TRACE_CONTEXT EnableInfo,\n    _In_ PCEVENT_DESCRIPTOR EventDescriptor\n    )\n{\n    return McGenLevelKeywordEnabled(EnableInfo, EventDescriptor->Level, EventDescriptor->Keyword);\n}\n#endif // MCGEN_EVENT_ENABLED_DEF\n\n#if !defined(MCGEN_CONTROL_CALLBACK)\n#define MCGEN_CONTROL_CALLBACK\n\n// This function is for use by MC-generated code and should not be used directly.\nDECLSPEC_NOINLINE __inline\nVOID\n__stdcall\nMcGenControlCallbackV2(\n    _In_ LPCGUID SourceId,\n    _In_ ULONG ControlCode,\n    _In_ UCHAR Level,\n    _In_ ULONGLONG MatchAnyKeyword,\n    _In_ ULONGLONG MatchAllKeyword,\n    _In_opt_ PEVENT_FILTER_DESCRIPTOR FilterData,\n    _Inout_opt_ PVOID CallbackContext\n    )\n/*++\n\nRoutine Description:\n\n    This is the notification callback for Windows Vista and later.\n\nArguments:\n\n    SourceId - The GUID that identifies the session that enabled the provider.\n\n    ControlCode - The parameter indicates whether the provider\n                  is being enabled or disabled.\n\n    Level - The level at which the event is enabled.\n\n    MatchAnyKeyword - The bitmask of keywords that the provider uses to\n                      determine the category of events that it writes.\n\n    MatchAllKeyword - This bitmask additionally restricts the category\n                      of events that the provider writes.\n\n    FilterData - The provider-defined data.\n\n    CallbackContext - The context of the callback that is defined when the provider\n                      called EtwRegister to register itself.\n\nRemarks:\n\n    ETW calls this function to notify provider of enable/disable\n\n--*/\n{\n    PMCGEN_TRACE_CONTEXT Ctx = (PMCGEN_TRACE_CONTEXT)CallbackContext;\n    ULONG Ix;\n#ifndef MCGEN_PRIVATE_ENABLE_CALLBACK_V2\n    UNREFERENCED_PARAMETER(SourceId);\n    UNREFERENCED_PARAMETER(FilterData);\n#endif\n\n    if (Ctx == NULL) {\n        return;\n    }\n\n    switch (ControlCode) {\n\n        case EVENT_CONTROL_CODE_ENABLE_PROVIDER:\n            Ctx->Level = Level;\n            Ctx->MatchAnyKeyword = MatchAnyKeyword;\n            Ctx->MatchAllKeyword = MatchAllKeyword;\n            Ctx->IsEnabled = EVENT_CONTROL_CODE_ENABLE_PROVIDER;\n\n            for (Ix = 0; Ix < Ctx->EnableBitsCount; Ix += 1) {\n                if (McGenLevelKeywordEnabled(Ctx, Ctx->EnableLevel[Ix], Ctx->EnableKeyWords[Ix]) != FALSE) {\n                    Ctx->EnableBitMask[Ix >> 5] |= (1 << (Ix % 32));\n                } else {\n                    Ctx->EnableBitMask[Ix >> 5] &= ~(1 << (Ix % 32));\n                }\n            }\n            break;\n\n        case EVENT_CONTROL_CODE_DISABLE_PROVIDER:\n            Ctx->IsEnabled = EVENT_CONTROL_CODE_DISABLE_PROVIDER;\n            Ctx->Level = 0;\n            Ctx->MatchAnyKeyword = 0;\n            Ctx->MatchAllKeyword = 0;\n            if (Ctx->EnableBitsCount > 0) {\n#pragma warning(suppress: 26451) // Arithmetic overflow cannot occur, no matter the value of EnableBitCount\n                RtlZeroMemory(Ctx->EnableBitMask, (((Ctx->EnableBitsCount - 1) / 32) + 1) * sizeof(ULONG));\n            }\n            break;\n\n        default:\n            break;\n    }\n\n#ifdef MCGEN_PRIVATE_ENABLE_CALLBACK_V2\n    //\n    // Call user defined callback\n    //\n    MCGEN_PRIVATE_ENABLE_CALLBACK_V2(\n        SourceId,\n        ControlCode,\n        Level,\n        MatchAnyKeyword,\n        MatchAllKeyword,\n        FilterData,\n        CallbackContext\n        );\n#endif // MCGEN_PRIVATE_ENABLE_CALLBACK_V2\n\n    return;\n}\n\n#endif // MCGEN_CONTROL_CALLBACK\n\n#ifndef _mcgen_PENABLECALLBACK\n  #if MCGEN_USE_KERNEL_MODE_APIS\n    #define _mcgen_PENABLECALLBACK      PETWENABLECALLBACK\n  #else\n    #define _mcgen_PENABLECALLBACK      PENABLECALLBACK\n  #endif\n#endif // _mcgen_PENABLECALLBACK\n\n#if !defined(_mcgen_PASTE2)\n// This macro is for use by MC-generated code and should not be used directly.\n#define _mcgen_PASTE2(a, b) _mcgen_PASTE2_imp(a, b)\n#define _mcgen_PASTE2_imp(a, b) a##b\n#endif // _mcgen_PASTE2\n\n#if !defined(_mcgen_PASTE3)\n// This macro is for use by MC-generated code and should not be used directly.\n#define _mcgen_PASTE3(a, b, c) _mcgen_PASTE3_imp(a, b, c)\n#define _mcgen_PASTE3_imp(a, b, c) a##b##_##c\n#endif // _mcgen_PASTE3\n\n//\n// Macro validation\n//\n\n// Validate MCGEN_EVENTREGISTER:\n\n// Trigger an error if MCGEN_EVENTREGISTER is not an unqualified (simple) identifier:\nstruct _mcgen_PASTE2(MCGEN_EVENTREGISTER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTREGISTER);\n\n// Trigger an error if MCGEN_EVENTREGISTER is redefined:\ntypedef struct _mcgen_PASTE2(MCGEN_EVENTREGISTER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTREGISTER)\n    MCGEN_EVENTREGISTER_must_not_be_redefined_between_headers;\n\n// Trigger an error if MCGEN_EVENTREGISTER is defined as a function-like macro:\ntypedef void MCGEN_EVENTREGISTER_must_not_be_a_functionLike_macro_MCGEN_EVENTREGISTER;\ntypedef int _mcgen_PASTE2(MCGEN_EVENTREGISTER_must_not_be_a_functionLike_macro_, MCGEN_EVENTREGISTER);\n\n// Validate MCGEN_EVENTUNREGISTER:\n\n// Trigger an error if MCGEN_EVENTUNREGISTER is not an unqualified (simple) identifier:\nstruct _mcgen_PASTE2(MCGEN_EVENTUNREGISTER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTUNREGISTER);\n\n// Trigger an error if MCGEN_EVENTUNREGISTER is redefined:\ntypedef struct _mcgen_PASTE2(MCGEN_EVENTUNREGISTER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTUNREGISTER)\n    MCGEN_EVENTUNREGISTER_must_not_be_redefined_between_headers;\n\n// Trigger an error if MCGEN_EVENTUNREGISTER is defined as a function-like macro:\ntypedef void MCGEN_EVENTUNREGISTER_must_not_be_a_functionLike_macro_MCGEN_EVENTUNREGISTER;\ntypedef int _mcgen_PASTE2(MCGEN_EVENTUNREGISTER_must_not_be_a_functionLike_macro_, MCGEN_EVENTUNREGISTER);\n\n// Validate MCGEN_EVENTSETINFORMATION:\n\n// Trigger an error if MCGEN_EVENTSETINFORMATION is not an unqualified (simple) identifier:\nstruct _mcgen_PASTE2(MCGEN_EVENTSETINFORMATION_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTSETINFORMATION);\n\n// Trigger an error if MCGEN_EVENTSETINFORMATION is redefined:\ntypedef struct _mcgen_PASTE2(MCGEN_EVENTSETINFORMATION_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTSETINFORMATION)\n    MCGEN_EVENTSETINFORMATION_must_not_be_redefined_between_headers;\n\n// Trigger an error if MCGEN_EVENTSETINFORMATION is defined as a function-like macro:\ntypedef void MCGEN_EVENTSETINFORMATION_must_not_be_a_functionLike_macro_MCGEN_EVENTSETINFORMATION;\ntypedef int _mcgen_PASTE2(MCGEN_EVENTSETINFORMATION_must_not_be_a_functionLike_macro_, MCGEN_EVENTSETINFORMATION);\n\n// Validate MCGEN_EVENTWRITETRANSFER:\n\n// Trigger an error if MCGEN_EVENTWRITETRANSFER is not an unqualified (simple) identifier:\nstruct _mcgen_PASTE2(MCGEN_EVENTWRITETRANSFER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTWRITETRANSFER);\n\n// Trigger an error if MCGEN_EVENTWRITETRANSFER is redefined:\ntypedef struct _mcgen_PASTE2(MCGEN_EVENTWRITETRANSFER_definition_must_be_an_unqualified_identifier_, MCGEN_EVENTWRITETRANSFER)\n    MCGEN_EVENTWRITETRANSFER_must_not_be_redefined_between_headers;;\n\n// Trigger an error if MCGEN_EVENTWRITETRANSFER is defined as a function-like macro:\ntypedef void MCGEN_EVENTWRITETRANSFER_must_not_be_a_functionLike_macro_MCGEN_EVENTWRITETRANSFER;\ntypedef int _mcgen_PASTE2(MCGEN_EVENTWRITETRANSFER_must_not_be_a_functionLike_macro_, MCGEN_EVENTWRITETRANSFER);\n\n#ifndef McGenEventWrite_def\n#define McGenEventWrite_def\n\n// This macro is for use by MC-generated code and should not be used directly.\n#define McGenEventWrite _mcgen_PASTE2(McGenEventWrite_, MCGEN_EVENTWRITETRANSFER)\n\n// This function is for use by MC-generated code and should not be used directly.\nDECLSPEC_NOINLINE __inline\nULONG __stdcall\nMcGenEventWrite(\n    _In_ PMCGEN_TRACE_CONTEXT Context,\n    _In_ PCEVENT_DESCRIPTOR Descriptor,\n    _In_opt_ LPCGUID ActivityId,\n    _In_range_(1, 128) ULONG EventDataCount,\n    _Pre_cap_(EventDataCount) EVENT_DATA_DESCRIPTOR* EventData\n    )\n{\n    const USHORT UNALIGNED* Traits;\n\n    // Some customized MCGEN_EVENTWRITETRANSFER macros might ignore ActivityId.\n    UNREFERENCED_PARAMETER(ActivityId);\n\n    Traits = (const USHORT UNALIGNED*)(UINT_PTR)Context->Logger;\n\n    if (Traits == NULL) {\n        EventData[0].Ptr = 0;\n        EventData[0].Size = 0;\n        EventData[0].Reserved = 0;\n    } else {\n        EventData[0].Ptr = (ULONG_PTR)Traits;\n        EventData[0].Size = *Traits;\n        EventData[0].Reserved = 2; // EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA\n    }\n\n    return MCGEN_EVENTWRITETRANSFER(\n        Context->RegistrationHandle,\n        Descriptor,\n        ActivityId,\n        NULL,\n        EventDataCount,\n        EventData);\n}\n#endif // McGenEventWrite_def\n\n#if !defined(McGenEventRegisterUnregister)\n#define McGenEventRegisterUnregister\n\n// This macro is for use by MC-generated code and should not be used directly.\n#define McGenEventRegister _mcgen_PASTE2(McGenEventRegister_, MCGEN_EVENTREGISTER)\n\n#pragma warning(push)\n#pragma warning(disable:6103)\n// This function is for use by MC-generated code and should not be used directly.\nDECLSPEC_NOINLINE __inline\nULONG __stdcall\nMcGenEventRegister(\n    _In_ LPCGUID ProviderId,\n    _In_opt_ _mcgen_PENABLECALLBACK EnableCallback,\n    _In_opt_ PVOID CallbackContext,\n    _Inout_ PREGHANDLE RegHandle\n    )\n/*++\n\nRoutine Description:\n\n    This function registers the provider with ETW.\n\nArguments:\n\n    ProviderId - Provider ID to register with ETW.\n\n    EnableCallback - Callback to be used.\n\n    CallbackContext - Context for the callback.\n\n    RegHandle - Pointer to registration handle.\n\nRemarks:\n\n    Should not be called if the provider is already registered (i.e. should not\n    be called if *RegHandle != 0). Repeatedly registering a provider is a bug\n    and may indicate a race condition. However, for compatibility with previous\n    behavior, this function will return SUCCESS in this case.\n\n--*/\n{\n    ULONG Error;\n\n    if (*RegHandle != 0)\n    {\n        Error = 0; // ERROR_SUCCESS\n    }\n    else\n    {\n        Error = MCGEN_EVENTREGISTER(ProviderId, EnableCallback, CallbackContext, RegHandle);\n    }\n\n    return Error;\n}\n#pragma warning(pop)\n\n// This macro is for use by MC-generated code and should not be used directly.\n#define McGenEventUnregister _mcgen_PASTE2(McGenEventUnregister_, MCGEN_EVENTUNREGISTER)\n\n// This function is for use by MC-generated code and should not be used directly.\nDECLSPEC_NOINLINE __inline\nULONG __stdcall\nMcGenEventUnregister(_Inout_ PREGHANDLE RegHandle)\n/*++\n\nRoutine Description:\n\n    Unregister from ETW and set *RegHandle = 0.\n\nArguments:\n\n    RegHandle - the pointer to the provider registration handle\n\nRemarks:\n\n    If provider has not been registered (i.e. if *RegHandle == 0),\n    return SUCCESS. It is safe to call McGenEventUnregister even if the\n    call to McGenEventRegister returned an error.\n\n--*/\n{\n    ULONG Error;\n\n    if(*RegHandle == 0)\n    {\n        Error = 0; // ERROR_SUCCESS\n    }\n    else\n    {\n        Error = MCGEN_EVENTUNREGISTER(*RegHandle);\n        *RegHandle = (REGHANDLE)0;\n    }\n\n    return Error;\n}\n\n#endif // McGenEventRegisterUnregister\n\n#ifndef _mcgen_EVENT_BIT_SET\n  #if defined(_M_IX86) || defined(_M_X64)\n    // This macro is for use by MC-generated code and should not be used directly.\n    #define _mcgen_EVENT_BIT_SET(EnableBits, BitPosition) ((((const unsigned char*)EnableBits)[BitPosition >> 3] & (1u << (BitPosition & 7))) != 0)\n  #else // CPU type\n    // This macro is for use by MC-generated code and should not be used directly.\n    #define _mcgen_EVENT_BIT_SET(EnableBits, BitPosition) ((EnableBits[BitPosition >> 5] & (1u << (BitPosition & 31))) != 0)\n  #endif // CPU type\n#endif // _mcgen_EVENT_BIT_SET\n\n#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n// Provider \"microsoft-windows-mimalloc\" event count 2\n//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n// Provider GUID = 138f4dbb-ee04-4899-aa0a-572ad4475779\nEXTERN_C __declspec(selectany) const GUID ETW_MI_Provider = {0x138f4dbb, 0xee04, 0x4899, {0xaa, 0x0a, 0x57, 0x2a, 0xd4, 0x47, 0x57, 0x79}};\n\n#ifndef ETW_MI_Provider_Traits\n#define ETW_MI_Provider_Traits NULL\n#endif // ETW_MI_Provider_Traits\n\n//\n// Event Descriptors\n//\nEXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ETW_MI_ALLOC = {0x64, 0x1, 0x0, 0x4, 0x0, 0x0, 0x0};\n#define ETW_MI_ALLOC_value 0x64\nEXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ETW_MI_FREE = {0x65, 0x1, 0x0, 0x4, 0x0, 0x0, 0x0};\n#define ETW_MI_FREE_value 0x65\n\n//\n// MCGEN_DISABLE_PROVIDER_CODE_GENERATION macro:\n// Define this macro to have the compiler skip the generated functions in this\n// header.\n//\n#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n//\n// Event Enablement Bits\n// These variables are for use by MC-generated code and should not be used directly.\n//\nEXTERN_C __declspec(selectany) DECLSPEC_CACHEALIGN ULONG microsoft_windows_mimallocEnableBits[1];\nEXTERN_C __declspec(selectany) const ULONGLONG microsoft_windows_mimallocKeywords[1] = {0x0};\nEXTERN_C __declspec(selectany) const unsigned char microsoft_windows_mimallocLevels[1] = {4};\n\n//\n// Provider context\n//\nEXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT ETW_MI_Provider_Context = {0, (ULONG_PTR)ETW_MI_Provider_Traits, 0, 0, 0, 0, 0, 0, 1, microsoft_windows_mimallocEnableBits, microsoft_windows_mimallocKeywords, microsoft_windows_mimallocLevels};\n\n//\n// Provider REGHANDLE\n//\n#define microsoft_windows_mimallocHandle (ETW_MI_Provider_Context.RegistrationHandle)\n\n//\n// This macro is set to 0, indicating that the EventWrite[Name] macros do not\n// have an Activity parameter. This is controlled by the -km and -um options.\n//\n#define ETW_MI_Provider_EventWriteActivity 0\n\n//\n// Register with ETW using the control GUID specified in the manifest.\n// Invoke this macro during module initialization (i.e. program startup,\n// DLL process attach, or driver load) to initialize the provider.\n// Note that if this function returns an error, the error means that\n// will not work, but no action needs to be taken -- even if EventRegister\n// returns an error, it is generally safe to use EventWrite and\n// EventUnregister macros (they will be no-ops if EventRegister failed).\n//\n#ifndef EventRegistermicrosoft_windows_mimalloc\n#define EventRegistermicrosoft_windows_mimalloc() McGenEventRegister(&ETW_MI_Provider, McGenControlCallbackV2, &ETW_MI_Provider_Context, &microsoft_windows_mimallocHandle)\n#endif\n\n//\n// Register with ETW using a specific control GUID (i.e. a GUID other than what\n// is specified in the manifest). Advanced scenarios only.\n//\n#ifndef EventRegisterByGuidmicrosoft_windows_mimalloc\n#define EventRegisterByGuidmicrosoft_windows_mimalloc(Guid) McGenEventRegister(&(Guid), McGenControlCallbackV2, &ETW_MI_Provider_Context, &microsoft_windows_mimallocHandle)\n#endif\n\n//\n// Unregister with ETW and close the provider.\n// Invoke this macro during module shutdown (i.e. program exit, DLL process\n// detach, or driver unload) to unregister the provider.\n// Note that you MUST call EventUnregister before DLL or driver unload\n// (not optional): failure to unregister a provider before DLL or driver unload\n// will result in crashes.\n//\n#ifndef EventUnregistermicrosoft_windows_mimalloc\n#define EventUnregistermicrosoft_windows_mimalloc() McGenEventUnregister(&microsoft_windows_mimallocHandle)\n#endif\n\n//\n// MCGEN_ENABLE_FORCONTEXT_CODE_GENERATION macro:\n// Define this macro to enable support for caller-allocated provider context.\n//\n#ifdef MCGEN_ENABLE_FORCONTEXT_CODE_GENERATION\n\n//\n// Advanced scenarios: Caller-allocated provider context.\n// Use when multiple differently-configured provider handles are needed,\n// e.g. for container-aware drivers, one context per container.\n//\n// Usage:\n//\n// - Caller enables the feature before including this header, e.g.\n//   #define MCGEN_ENABLE_FORCONTEXT_CODE_GENERATION 1\n// - Caller allocates memory, e.g. pContext = malloc(sizeof(McGenContext_microsoft_windows_mimalloc));\n// - Caller registers the provider, e.g. EventRegistermicrosoft_windows_mimalloc_ForContext(pContext);\n// - Caller writes events, e.g. EventWriteMyEvent_ForContext(pContext, ...);\n// - Caller unregisters, e.g. EventUnregistermicrosoft_windows_mimalloc_ForContext(pContext);\n// - Caller frees memory, e.g. free(pContext);\n//\n\ntypedef struct tagMcGenContext_microsoft_windows_mimalloc {\n    // The fields of this structure are subject to change and should\n    // not be accessed directly. To access the provider's REGHANDLE,\n    // use microsoft_windows_mimallocHandle_ForContext(pContext).\n    MCGEN_TRACE_CONTEXT Context;\n    ULONG EnableBits[1];\n} McGenContext_microsoft_windows_mimalloc;\n\n#define EventRegistermicrosoft_windows_mimalloc_ForContext(pContext)             _mcgen_PASTE2(_mcgen_RegisterForContext_microsoft_windows_mimalloc_, MCGEN_EVENTREGISTER)(&ETW_MI_Provider, pContext)\n#define EventRegisterByGuidmicrosoft_windows_mimalloc_ForContext(Guid, pContext) _mcgen_PASTE2(_mcgen_RegisterForContext_microsoft_windows_mimalloc_, MCGEN_EVENTREGISTER)(&(Guid), pContext)\n#define EventUnregistermicrosoft_windows_mimalloc_ForContext(pContext)           McGenEventUnregister(&(pContext)->Context.RegistrationHandle)\n\n//\n// Provider REGHANDLE for caller-allocated context.\n//\n#define microsoft_windows_mimallocHandle_ForContext(pContext) ((pContext)->Context.RegistrationHandle)\n\n// This function is for use by MC-generated code and should not be used directly.\n// Initialize and register the caller-allocated context.\n__inline\nULONG __stdcall\n_mcgen_PASTE2(_mcgen_RegisterForContext_microsoft_windows_mimalloc_, MCGEN_EVENTREGISTER)(\n    _In_ LPCGUID pProviderId,\n    _Out_ McGenContext_microsoft_windows_mimalloc* pContext)\n{\n    RtlZeroMemory(pContext, sizeof(*pContext));\n    pContext->Context.Logger = (ULONG_PTR)ETW_MI_Provider_Traits;\n    pContext->Context.EnableBitsCount = 1;\n    pContext->Context.EnableBitMask = pContext->EnableBits;\n    pContext->Context.EnableKeyWords = microsoft_windows_mimallocKeywords;\n    pContext->Context.EnableLevel = microsoft_windows_mimallocLevels;\n    return McGenEventRegister(\n        pProviderId,\n        McGenControlCallbackV2,\n        &pContext->Context,\n        &pContext->Context.RegistrationHandle);\n}\n\n// This function is for use by MC-generated code and should not be used directly.\n// Trigger a compile error if called with the wrong parameter type.\nFORCEINLINE\n_Ret_ McGenContext_microsoft_windows_mimalloc*\n_mcgen_CheckContextType_microsoft_windows_mimalloc(_In_ McGenContext_microsoft_windows_mimalloc* pContext)\n{\n    return pContext;\n}\n\n#endif // MCGEN_ENABLE_FORCONTEXT_CODE_GENERATION\n\n//\n// Enablement check macro for event \"ETW_MI_ALLOC\"\n//\n#define EventEnabledETW_MI_ALLOC() _mcgen_EVENT_BIT_SET(microsoft_windows_mimallocEnableBits, 0)\n#define EventEnabledETW_MI_ALLOC_ForContext(pContext) _mcgen_EVENT_BIT_SET(_mcgen_CheckContextType_microsoft_windows_mimalloc(pContext)->EnableBits, 0)\n\n//\n// Event write macros for event \"ETW_MI_ALLOC\"\n//\n#define EventWriteETW_MI_ALLOC(Address, Size) \\\n        MCGEN_EVENT_ENABLED(ETW_MI_ALLOC) \\\n        ? _mcgen_TEMPLATE_FOR_ETW_MI_ALLOC(&ETW_MI_Provider_Context, &ETW_MI_ALLOC, Address, Size) : 0\n#define EventWriteETW_MI_ALLOC_AssumeEnabled(Address, Size) \\\n        _mcgen_TEMPLATE_FOR_ETW_MI_ALLOC(&ETW_MI_Provider_Context, &ETW_MI_ALLOC, Address, Size)\n#define EventWriteETW_MI_ALLOC_ForContext(pContext, Address, Size) \\\n        MCGEN_EVENT_ENABLED_FORCONTEXT(pContext, ETW_MI_ALLOC) \\\n        ? _mcgen_TEMPLATE_FOR_ETW_MI_ALLOC(&(pContext)->Context, &ETW_MI_ALLOC, Address, Size) : 0\n#define EventWriteETW_MI_ALLOC_ForContextAssumeEnabled(pContext, Address, Size) \\\n        _mcgen_TEMPLATE_FOR_ETW_MI_ALLOC(&_mcgen_CheckContextType_microsoft_windows_mimalloc(pContext)->Context, &ETW_MI_ALLOC, Address, Size)\n\n// This macro is for use by MC-generated code and should not be used directly.\n#define _mcgen_TEMPLATE_FOR_ETW_MI_ALLOC _mcgen_PASTE2(McTemplateU0xx_, MCGEN_EVENTWRITETRANSFER)\n\n//\n// Enablement check macro for event \"ETW_MI_FREE\"\n//\n#define EventEnabledETW_MI_FREE() _mcgen_EVENT_BIT_SET(microsoft_windows_mimallocEnableBits, 0)\n#define EventEnabledETW_MI_FREE_ForContext(pContext) _mcgen_EVENT_BIT_SET(_mcgen_CheckContextType_microsoft_windows_mimalloc(pContext)->EnableBits, 0)\n\n//\n// Event write macros for event \"ETW_MI_FREE\"\n//\n#define EventWriteETW_MI_FREE(Address, Size) \\\n        MCGEN_EVENT_ENABLED(ETW_MI_FREE) \\\n        ? _mcgen_TEMPLATE_FOR_ETW_MI_FREE(&ETW_MI_Provider_Context, &ETW_MI_FREE, Address, Size) : 0\n#define EventWriteETW_MI_FREE_AssumeEnabled(Address, Size) \\\n        _mcgen_TEMPLATE_FOR_ETW_MI_FREE(&ETW_MI_Provider_Context, &ETW_MI_FREE, Address, Size)\n#define EventWriteETW_MI_FREE_ForContext(pContext, Address, Size) \\\n        MCGEN_EVENT_ENABLED_FORCONTEXT(pContext, ETW_MI_FREE) \\\n        ? _mcgen_TEMPLATE_FOR_ETW_MI_FREE(&(pContext)->Context, &ETW_MI_FREE, Address, Size) : 0\n#define EventWriteETW_MI_FREE_ForContextAssumeEnabled(pContext, Address, Size) \\\n        _mcgen_TEMPLATE_FOR_ETW_MI_FREE(&_mcgen_CheckContextType_microsoft_windows_mimalloc(pContext)->Context, &ETW_MI_FREE, Address, Size)\n\n// This macro is for use by MC-generated code and should not be used directly.\n#define _mcgen_TEMPLATE_FOR_ETW_MI_FREE _mcgen_PASTE2(McTemplateU0xx_, MCGEN_EVENTWRITETRANSFER)\n\n#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n//\n// MCGEN_DISABLE_PROVIDER_CODE_GENERATION macro:\n// Define this macro to have the compiler skip the generated functions in this\n// header.\n//\n#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n//\n// Template Functions\n//\n\n//\n// Function for template \"ETW_CUSTOM_HEAP_ALLOC_DATA\" (and possibly others).\n// This function is for use by MC-generated code and should not be used directly.\n//\n#ifndef McTemplateU0xx_def\n#define McTemplateU0xx_def\nETW_INLINE\nULONG\n_mcgen_PASTE2(McTemplateU0xx_, MCGEN_EVENTWRITETRANSFER)(\n    _In_ PMCGEN_TRACE_CONTEXT Context,\n    _In_ PCEVENT_DESCRIPTOR Descriptor,\n    _In_ const unsigned __int64  _Arg0,\n    _In_ const unsigned __int64  _Arg1\n    )\n{\n#define McTemplateU0xx_ARGCOUNT 2\n\n    EVENT_DATA_DESCRIPTOR EventData[McTemplateU0xx_ARGCOUNT + 1];\n\n    EventDataDescCreate(&EventData[1],&_Arg0, sizeof(const unsigned __int64)  );\n\n    EventDataDescCreate(&EventData[2],&_Arg1, sizeof(const unsigned __int64)  );\n\n    return McGenEventWrite(Context, Descriptor, NULL, McTemplateU0xx_ARGCOUNT + 1, EventData);\n}\n#endif // McTemplateU0xx_def\n\n#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION\n\n#if defined(__cplusplus)\n}\n#endif\n"
  },
  {
    "path": "src/prim/windows/prim.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n// This file is included in `src/prim/prim.c`\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"\n#include <stdio.h>   // fputs, stderr\n\n// xbox has no console IO\n#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)\n#define MI_HAS_CONSOLE_IO\n#endif\n\n//---------------------------------------------\n// Dynamically bind Windows API points for portability\n//---------------------------------------------\n\n// We use VirtualAlloc2 for aligned allocation, but it is only supported on Windows 10 and Windows Server 2016.\n// So, we need to look it up dynamically to run on older systems. (use __stdcall for 32-bit compatibility)\n// NtAllocateVirtualAllocEx is used for huge OS page allocation (1GiB)\n// We define a minimal MEM_EXTENDED_PARAMETER ourselves in order to be able to compile with older SDK's.\ntypedef enum MI_MEM_EXTENDED_PARAMETER_TYPE_E {\n  MiMemExtendedParameterInvalidType = 0,\n  MiMemExtendedParameterAddressRequirements,\n  MiMemExtendedParameterNumaNode,\n  MiMemExtendedParameterPartitionHandle,\n  MiMemExtendedParameterUserPhysicalHandle,\n  MiMemExtendedParameterAttributeFlags,\n  MiMemExtendedParameterMax\n} MI_MEM_EXTENDED_PARAMETER_TYPE;\n\ntypedef struct DECLSPEC_ALIGN(8) MI_MEM_EXTENDED_PARAMETER_S {\n  struct { DWORD64 Type : 8; DWORD64 Reserved : 56; } Type;\n  union  { DWORD64 ULong64; PVOID Pointer; SIZE_T Size; HANDLE Handle; DWORD ULong; } Arg;\n} MI_MEM_EXTENDED_PARAMETER;\n\ntypedef struct MI_MEM_ADDRESS_REQUIREMENTS_S {\n  PVOID  LowestStartingAddress;\n  PVOID  HighestEndingAddress;\n  SIZE_T Alignment;\n} MI_MEM_ADDRESS_REQUIREMENTS;\n\n#define MI_MEM_EXTENDED_PARAMETER_NONPAGED_HUGE   0x00000010\n\n#include <winternl.h>\ntypedef PVOID (__stdcall *PVirtualAlloc2)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MI_MEM_EXTENDED_PARAMETER*, ULONG);\ntypedef LONG  (__stdcall *PNtAllocateVirtualMemoryEx)(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG, MI_MEM_EXTENDED_PARAMETER*, ULONG);  // avoid NTSTATUS as it is not defined on xbox (pr #1084)\nstatic PVirtualAlloc2 pVirtualAlloc2 = NULL;\nstatic PNtAllocateVirtualMemoryEx pNtAllocateVirtualMemoryEx = NULL;\n\n// Similarly, GetNumaProcessorNodeEx is only supported since Windows 7  (and GetNumaNodeProcessorMask is not supported on xbox)\ntypedef struct MI_PROCESSOR_NUMBER_S { WORD Group; BYTE Number; BYTE Reserved; } MI_PROCESSOR_NUMBER;\n\ntypedef VOID (__stdcall *PGetCurrentProcessorNumberEx)(MI_PROCESSOR_NUMBER* ProcNumber);\ntypedef BOOL (__stdcall *PGetNumaProcessorNodeEx)(MI_PROCESSOR_NUMBER* Processor, PUSHORT NodeNumber);\ntypedef BOOL (__stdcall* PGetNumaNodeProcessorMaskEx)(USHORT Node, PGROUP_AFFINITY ProcessorMask);\ntypedef BOOL (__stdcall *PGetNumaProcessorNode)(UCHAR Processor, PUCHAR NodeNumber);\ntypedef BOOL (__stdcall* PGetNumaNodeProcessorMask)(UCHAR Node, PULONGLONG ProcessorMask);\ntypedef BOOL (__stdcall* PGetNumaHighestNodeNumber)(PULONG Node);\nstatic PGetCurrentProcessorNumberEx pGetCurrentProcessorNumberEx = NULL;\nstatic PGetNumaProcessorNodeEx      pGetNumaProcessorNodeEx = NULL;\nstatic PGetNumaNodeProcessorMaskEx  pGetNumaNodeProcessorMaskEx = NULL;\nstatic PGetNumaProcessorNode        pGetNumaProcessorNode = NULL;\nstatic PGetNumaNodeProcessorMask    pGetNumaNodeProcessorMask = NULL;\nstatic PGetNumaHighestNodeNumber    pGetNumaHighestNodeNumber = NULL;\n\n// Not available on xbox\ntypedef SIZE_T(__stdcall* PGetLargePageMinimum)(VOID);\nstatic PGetLargePageMinimum pGetLargePageMinimum = NULL;\n\n// Available after Windows XP\ntypedef BOOL (__stdcall *PGetPhysicallyInstalledSystemMemory)( PULONGLONG TotalMemoryInKilobytes );\n\n//---------------------------------------------\n// Enable large page support dynamically (if possible)\n//---------------------------------------------\n\nstatic bool win_enable_large_os_pages(size_t* large_page_size)\n{\n  static bool large_initialized = false;\n  if (large_initialized) return (_mi_os_large_page_size() > 0);\n  large_initialized = true;\n  if (pGetLargePageMinimum==NULL) return false;  // no large page support (xbox etc.)\n\n  // Try to see if large OS pages are supported\n  // To use large pages on Windows, we first need access permission\n  // Set \"Lock pages in memory\" permission in the group policy editor\n  // <https://devblogs.microsoft.com/oldnewthing/20110128-00/?p=11643>\n  unsigned long err = 0;\n  HANDLE token = NULL;\n  BOOL ok = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token);\n  if (ok) {\n    TOKEN_PRIVILEGES tp;\n    ok = LookupPrivilegeValue(NULL, TEXT(\"SeLockMemoryPrivilege\"), &tp.Privileges[0].Luid);\n    if (ok) {\n      tp.PrivilegeCount = 1;\n      tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n      ok = AdjustTokenPrivileges(token, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0);\n      if (ok) {\n        err = GetLastError();\n        ok = (err == ERROR_SUCCESS);\n        if (ok && large_page_size != NULL && pGetLargePageMinimum != NULL) {\n          *large_page_size = (*pGetLargePageMinimum)();\n        }\n      }\n    }\n    CloseHandle(token);\n  }\n  if (!ok) {\n    if (err == 0) err = GetLastError();\n    _mi_warning_message(\"cannot enable large OS page support, error %lu\\n\", err);\n  }\n  return (ok!=0);\n}\n\n\n//---------------------------------------------\n// Initialize\n//---------------------------------------------\n\nvoid _mi_prim_mem_init( mi_os_mem_config_t* config )\n{\n  config->has_overcommit = false;\n  config->has_partial_free = false;\n  config->has_virtual_reserve = true;\n  // get the page size\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  if (si.dwPageSize > 0) { config->page_size = si.dwPageSize; }\n  if (si.dwAllocationGranularity > 0) { config->alloc_granularity = si.dwAllocationGranularity; }\n  // get virtual address bits\n  if ((uintptr_t)si.lpMaximumApplicationAddress > 0) {\n    const size_t vbits = MI_SIZE_BITS - mi_clz((uintptr_t)si.lpMaximumApplicationAddress);\n    config->virtual_address_bits = vbits;\n  }\n\n  // get the VirtualAlloc2 function\n  HINSTANCE  hDll;\n  hDll = LoadLibrary(TEXT(\"kernelbase.dll\"));\n  if (hDll != NULL) {\n    // use VirtualAlloc2FromApp if possible as it is available to Windows store apps\n    pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, \"VirtualAlloc2FromApp\");\n    if (pVirtualAlloc2==NULL) pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, \"VirtualAlloc2\");\n    FreeLibrary(hDll);\n  }\n  // NtAllocateVirtualMemoryEx is used for huge page allocation\n  hDll = LoadLibrary(TEXT(\"ntdll.dll\"));\n  if (hDll != NULL) {\n    pNtAllocateVirtualMemoryEx = (PNtAllocateVirtualMemoryEx)(void (*)(void))GetProcAddress(hDll, \"NtAllocateVirtualMemoryEx\");\n    FreeLibrary(hDll);\n  }\n  // Try to use Win7+ numa API\n  hDll = LoadLibrary(TEXT(\"kernel32.dll\"));\n  if (hDll != NULL) {\n    pGetCurrentProcessorNumberEx = (PGetCurrentProcessorNumberEx)(void (*)(void))GetProcAddress(hDll, \"GetCurrentProcessorNumberEx\");\n    pGetNumaProcessorNodeEx = (PGetNumaProcessorNodeEx)(void (*)(void))GetProcAddress(hDll, \"GetNumaProcessorNodeEx\");\n    pGetNumaNodeProcessorMaskEx = (PGetNumaNodeProcessorMaskEx)(void (*)(void))GetProcAddress(hDll, \"GetNumaNodeProcessorMaskEx\");\n    pGetNumaProcessorNode = (PGetNumaProcessorNode)(void (*)(void))GetProcAddress(hDll, \"GetNumaProcessorNode\");\n    pGetNumaNodeProcessorMask = (PGetNumaNodeProcessorMask)(void (*)(void))GetProcAddress(hDll, \"GetNumaNodeProcessorMask\");\n    pGetNumaHighestNodeNumber = (PGetNumaHighestNodeNumber)(void (*)(void))GetProcAddress(hDll, \"GetNumaHighestNodeNumber\");\n    pGetLargePageMinimum = (PGetLargePageMinimum)(void (*)(void))GetProcAddress(hDll, \"GetLargePageMinimum\");\n    // Get physical memory (not available on XP, so check dynamically)\n    PGetPhysicallyInstalledSystemMemory pGetPhysicallyInstalledSystemMemory = (PGetPhysicallyInstalledSystemMemory)(void (*)(void))GetProcAddress(hDll,\"GetPhysicallyInstalledSystemMemory\");\n    if (pGetPhysicallyInstalledSystemMemory != NULL) {\n      ULONGLONG memInKiB = 0;\n      if ((*pGetPhysicallyInstalledSystemMemory)(&memInKiB)) {\n        if (memInKiB > 0 && memInKiB <= SIZE_MAX) {\n          config->physical_memory_in_kib = (size_t)memInKiB;\n        }\n      }\n    }\n    FreeLibrary(hDll);\n  }\n  // Enable large/huge OS page support?\n  if (mi_option_is_enabled(mi_option_allow_large_os_pages) || mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {\n    win_enable_large_os_pages(&config->large_page_size);\n  }\n}\n\n\n//---------------------------------------------\n// Free\n//---------------------------------------------\n\nint _mi_prim_free(void* addr, size_t size ) {\n  MI_UNUSED(size);\n  DWORD errcode = 0;\n  bool err = (VirtualFree(addr, 0, MEM_RELEASE) == 0);\n  if (err) { errcode = GetLastError(); }\n  if (errcode == ERROR_INVALID_ADDRESS) {\n    // In mi_os_mem_alloc_aligned the fallback path may have returned a pointer inside\n    // the memory region returned by VirtualAlloc; in that case we need to free using\n    // the start of the region.\n    MEMORY_BASIC_INFORMATION info; _mi_memzero_var(info);\n    VirtualQuery(addr, &info, sizeof(info));\n    if (info.AllocationBase < addr && ((uint8_t*)addr - (uint8_t*)info.AllocationBase) < (ptrdiff_t)MI_SEGMENT_SIZE) {\n      errcode = 0;\n      err = (VirtualFree(info.AllocationBase, 0, MEM_RELEASE) == 0);\n      if (err) { errcode = GetLastError(); }\n    }\n  }\n  return (int)errcode;\n}\n\n\n//---------------------------------------------\n// VirtualAlloc\n//---------------------------------------------\n\nstatic void* win_virtual_alloc_prim_once(void* addr, size_t size, size_t try_alignment, DWORD flags) {\n  #if (MI_INTPTR_SIZE >= 8)\n  // on 64-bit systems, try to use the virtual address area after 2TiB for 4MiB aligned allocations\n  if (addr == NULL) {\n    void* hint = _mi_os_get_aligned_hint(try_alignment,size);\n    if (hint != NULL) {\n      void* p = VirtualAlloc(hint, size, flags, PAGE_READWRITE);\n      if (p != NULL) return p;\n      _mi_verbose_message(\"warning: unable to allocate hinted aligned OS memory (%zu bytes, error code: 0x%x, address: %p, alignment: %zu, flags: 0x%x)\\n\", size, GetLastError(), hint, try_alignment, flags);\n      // fall through on error\n    }\n  }\n  #endif\n  // on modern Windows try use VirtualAlloc2 for aligned allocation\n  if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0 && pVirtualAlloc2 != NULL) {\n    MI_MEM_ADDRESS_REQUIREMENTS reqs = { 0, 0, 0 };\n    reqs.Alignment = try_alignment;\n    MI_MEM_EXTENDED_PARAMETER param = { {0, 0}, {0} };\n    param.Type.Type = MiMemExtendedParameterAddressRequirements;\n    param.Arg.Pointer = &reqs;\n    void* p = (*pVirtualAlloc2)(GetCurrentProcess(), addr, size, flags, PAGE_READWRITE, &param, 1);\n    if (p != NULL) return p;\n    _mi_warning_message(\"unable to allocate aligned OS memory (0x%zx bytes, error code: 0x%x, address: %p, alignment: 0x%zx, flags: 0x%x)\\n\", size, GetLastError(), addr, try_alignment, flags);\n    // fall through on error\n  }\n  // last resort\n  return VirtualAlloc(addr, size, flags, PAGE_READWRITE);\n}\n\nstatic bool win_is_out_of_memory_error(DWORD err) {\n  switch (err) {\n    case ERROR_COMMITMENT_MINIMUM:\n    case ERROR_COMMITMENT_LIMIT:\n    case ERROR_PAGEFILE_QUOTA:\n    case ERROR_NOT_ENOUGH_MEMORY:\n      return true;\n    default:\n      return false;\n  }\n}\n\nstatic void* win_virtual_alloc_prim(void* addr, size_t size, size_t try_alignment, DWORD flags) {\n  long max_retry_msecs = mi_option_get_clamp(mi_option_retry_on_oom, 0, 2000);  // at most 2 seconds\n  if (max_retry_msecs == 1) { max_retry_msecs = 100; }  // if one sets the option to \"true\"\n  for (long tries = 1; tries <= 10; tries++) {          // try at most 10 times (=2200ms)\n    void* p = win_virtual_alloc_prim_once(addr, size, try_alignment, flags);\n    if (p != NULL) {\n      // success, return the address\n      return p;\n    }\n    else if (max_retry_msecs > 0 && (try_alignment <= 2*MI_SEGMENT_ALIGN) &&\n              (flags&MEM_COMMIT) != 0 && (flags&MEM_LARGE_PAGES) == 0 &&\n              win_is_out_of_memory_error(GetLastError())) {\n      // if committing regular memory and being out-of-memory,\n      // keep trying for a bit in case memory frees up after all. See issue #894\n      _mi_warning_message(\"out-of-memory on OS allocation, try again... (attempt %lu, 0x%zx bytes, error code: 0x%x, address: %p, alignment: 0x%zx, flags: 0x%x)\\n\", tries, size, GetLastError(), addr, try_alignment, flags);\n      long sleep_msecs = tries*40;  // increasing waits\n      if (sleep_msecs > max_retry_msecs) { sleep_msecs = max_retry_msecs; }\n      max_retry_msecs -= sleep_msecs;\n      Sleep(sleep_msecs);\n    }\n    else {\n      // otherwise return with an error\n      break;\n    }\n  }\n  return NULL;\n}\n\nstatic void* win_virtual_alloc(void* addr, size_t size, size_t try_alignment, DWORD flags, bool large_only, bool allow_large, bool* is_large) {\n  mi_assert_internal(!(large_only && !allow_large));\n  static _Atomic(size_t) large_page_try_ok; // = 0;\n  void* p = NULL;\n  // Try to allocate large OS pages (2MiB) if allowed or required.\n  if ((large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages)))\n      && allow_large && (flags&MEM_COMMIT)!=0 && (flags&MEM_RESERVE)!=0)\n  {\n    size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok);\n    if (!large_only && try_ok > 0) {\n      // if a large page allocation fails, it seems the calls to VirtualAlloc get very expensive.\n      // therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times.\n      mi_atomic_cas_strong_acq_rel(&large_page_try_ok, &try_ok, try_ok - 1);\n    }\n    else {\n      // large OS pages must always reserve and commit.\n      *is_large = true;\n      p = win_virtual_alloc_prim(addr, size, try_alignment, flags | MEM_LARGE_PAGES);\n      if (large_only) return p;\n      // fall back to non-large page allocation on error (`p == NULL`).\n      if (p == NULL) {\n        mi_atomic_store_release(&large_page_try_ok,10UL);  // on error, don't try again for the next N allocations\n      }\n    }\n  }\n  // Fall back to regular page allocation\n  if (p == NULL) {\n    *is_large = ((flags&MEM_LARGE_PAGES) != 0);\n    p = win_virtual_alloc_prim(addr, size, try_alignment, flags);\n  }\n  //if (p == NULL) { _mi_warning_message(\"unable to allocate OS memory (%zu bytes, error code: 0x%x, address: %p, alignment: %zu, flags: 0x%x, large only: %d, allow large: %d)\\n\", size, GetLastError(), addr, try_alignment, flags, large_only, allow_large); }\n  return p;\n}\n\nint _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {\n  mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);\n  mi_assert_internal(commit || !allow_large);\n  mi_assert_internal(try_alignment > 0);\n  *is_zero = true;\n  int flags = MEM_RESERVE;\n  if (commit) { flags |= MEM_COMMIT; }\n  *addr = win_virtual_alloc(hint_addr, size, try_alignment, flags, false, allow_large, is_large);\n  return (*addr != NULL ? 0 : (int)GetLastError());\n}\n\n\n//---------------------------------------------\n// Commit/Reset/Protect\n//---------------------------------------------\n#ifdef _MSC_VER\n#pragma warning(disable:6250)   // suppress warning calling VirtualFree without MEM_RELEASE (for decommit)\n#endif\n\nint _mi_prim_commit(void* addr, size_t size, bool* is_zero) {\n  *is_zero = false;\n  /*\n  // zero'ing only happens on an initial commit... but checking upfront seems expensive..\n  _MEMORY_BASIC_INFORMATION meminfo; _mi_memzero_var(meminfo);\n  if (VirtualQuery(addr, &meminfo, size) > 0) {\n    if ((meminfo.State & MEM_COMMIT) == 0) {\n      *is_zero = true;\n    }\n  }\n  */\n  // commit\n  void* p = VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE);\n  if (p == NULL) return (int)GetLastError();\n  return 0;\n}\n\nint _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit) {\n  BOOL ok = VirtualFree(addr, size, MEM_DECOMMIT);\n  *needs_recommit = true;  // for safety, assume always decommitted even in the case of an error.\n  return (ok ? 0 : (int)GetLastError());\n}\n\nint _mi_prim_reset(void* addr, size_t size) {\n  void* p = VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE);\n  mi_assert_internal(p == addr);\n  #if 0\n  if (p != NULL) {\n    VirtualUnlock(addr,size); // VirtualUnlock after MEM_RESET removes the memory directly from the working set\n  }\n  #endif\n  return (p != NULL ? 0 : (int)GetLastError());\n}\n\nint _mi_prim_reuse(void* addr, size_t size) {\n  MI_UNUSED(addr); MI_UNUSED(size);\n  return 0;\n}\n\nint _mi_prim_protect(void* addr, size_t size, bool protect) {\n  DWORD oldprotect = 0;\n  BOOL ok = VirtualProtect(addr, size, protect ? PAGE_NOACCESS : PAGE_READWRITE, &oldprotect);\n  return (ok ? 0 : (int)GetLastError());\n}\n\n\n//---------------------------------------------\n// Huge page allocation\n//---------------------------------------------\n\nstatic void* _mi_prim_alloc_huge_os_pagesx(void* hint_addr, size_t size, int numa_node)\n{\n  const DWORD flags = MEM_LARGE_PAGES | MEM_COMMIT | MEM_RESERVE;\n\n  win_enable_large_os_pages(NULL);\n\n  MI_MEM_EXTENDED_PARAMETER params[3] = { {{0,0},{0}},{{0,0},{0}},{{0,0},{0}} };\n  // on modern Windows try use NtAllocateVirtualMemoryEx for 1GiB huge pages\n  static bool mi_huge_pages_available = true;\n  if (pNtAllocateVirtualMemoryEx != NULL && mi_huge_pages_available) {\n    params[0].Type.Type = MiMemExtendedParameterAttributeFlags;\n    params[0].Arg.ULong64 = MI_MEM_EXTENDED_PARAMETER_NONPAGED_HUGE;\n    ULONG param_count = 1;\n    if (numa_node >= 0) {\n      param_count++;\n      params[1].Type.Type = MiMemExtendedParameterNumaNode;\n      params[1].Arg.ULong = (unsigned)numa_node;\n    }\n    SIZE_T psize = size;\n    void* base = hint_addr;\n    LONG err = (*pNtAllocateVirtualMemoryEx)(GetCurrentProcess(), &base, &psize, flags, PAGE_READWRITE, params, param_count);\n    if (err == 0 && base != NULL) {\n      return base;\n    }\n    else {\n      // fall back to regular large pages\n      mi_huge_pages_available = false; // don't try further huge pages\n      _mi_warning_message(\"unable to allocate using huge (1GiB) pages, trying large (2MiB) pages instead (status 0x%lx)\\n\", err);\n    }\n  }\n  // on modern Windows try use VirtualAlloc2 for numa aware large OS page allocation\n  if (pVirtualAlloc2 != NULL && numa_node >= 0) {\n    params[0].Type.Type = MiMemExtendedParameterNumaNode;\n    params[0].Arg.ULong = (unsigned)numa_node;\n    return (*pVirtualAlloc2)(GetCurrentProcess(), hint_addr, size, flags, PAGE_READWRITE, params, 1);\n  }\n\n  // otherwise use regular virtual alloc on older windows\n  return VirtualAlloc(hint_addr, size, flags, PAGE_READWRITE);\n}\n\nint _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {\n  *is_zero = true;\n  *addr = _mi_prim_alloc_huge_os_pagesx(hint_addr,size,numa_node);\n  return (*addr != NULL ? 0 : (int)GetLastError());\n}\n\n\n//---------------------------------------------\n// Numa nodes\n//---------------------------------------------\n\nsize_t _mi_prim_numa_node(void) {\n  USHORT numa_node = 0;\n  if (pGetCurrentProcessorNumberEx != NULL && pGetNumaProcessorNodeEx != NULL) {\n    // Extended API is supported\n    MI_PROCESSOR_NUMBER pnum;\n    (*pGetCurrentProcessorNumberEx)(&pnum);\n    USHORT nnode = 0;\n    BOOL ok = (*pGetNumaProcessorNodeEx)(&pnum, &nnode);\n    if (ok) { numa_node = nnode; }\n  }\n  else if (pGetNumaProcessorNode != NULL) {\n    // Vista or earlier, use older API that is limited to 64 processors. Issue #277\n    DWORD pnum = GetCurrentProcessorNumber();\n    UCHAR nnode = 0;\n    BOOL ok = pGetNumaProcessorNode((UCHAR)pnum, &nnode);\n    if (ok) { numa_node = nnode; }\n  }\n  return numa_node;\n}\n\nsize_t _mi_prim_numa_node_count(void) {\n  ULONG numa_max = 0;\n  if (pGetNumaHighestNodeNumber!=NULL) {\n    (*pGetNumaHighestNodeNumber)(&numa_max);\n  }\n  // find the highest node number that has actual processors assigned to it. Issue #282\n  while (numa_max > 0) {\n    if (pGetNumaNodeProcessorMaskEx != NULL) {\n      // Extended API is supported\n      GROUP_AFFINITY affinity;\n      if ((*pGetNumaNodeProcessorMaskEx)((USHORT)numa_max, &affinity)) {\n        if (affinity.Mask != 0) break;  // found the maximum non-empty node\n      }\n    }\n    else {\n      // Vista or earlier, use older API that is limited to 64 processors.\n      ULONGLONG mask;\n      if (pGetNumaNodeProcessorMask != NULL) {\n        if ((*pGetNumaNodeProcessorMask)((UCHAR)numa_max, &mask)) {\n          if (mask != 0) break; // found the maximum non-empty node\n        }\n      };\n    }\n    // max node was invalid or had no processor assigned, try again\n    numa_max--;\n  }\n  return ((size_t)numa_max + 1);\n}\n\n\n//----------------------------------------------------------------\n// Clock\n//----------------------------------------------------------------\n\nstatic mi_msecs_t mi_to_msecs(LARGE_INTEGER t) {\n  static LARGE_INTEGER mfreq; // = 0\n  if (mfreq.QuadPart == 0LL) {\n    LARGE_INTEGER f;\n    QueryPerformanceFrequency(&f);\n    mfreq.QuadPart = f.QuadPart/1000LL;\n    if (mfreq.QuadPart == 0) mfreq.QuadPart = 1;\n  }\n  return (mi_msecs_t)(t.QuadPart / mfreq.QuadPart);\n}\n\nmi_msecs_t _mi_prim_clock_now(void) {\n  LARGE_INTEGER t;\n  QueryPerformanceCounter(&t);\n  return mi_to_msecs(t);\n}\n\n\n//----------------------------------------------------------------\n// Process Info\n//----------------------------------------------------------------\n\n#include <psapi.h>\n\nstatic mi_msecs_t filetime_msecs(const FILETIME* ftime) {\n  ULARGE_INTEGER i;\n  i.LowPart = ftime->dwLowDateTime;\n  i.HighPart = ftime->dwHighDateTime;\n  mi_msecs_t msecs = (i.QuadPart / 10000); // FILETIME is in 100 nano seconds\n  return msecs;\n}\n\ntypedef BOOL (WINAPI *PGetProcessMemoryInfo)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD);\nstatic PGetProcessMemoryInfo pGetProcessMemoryInfo = NULL;\n\nvoid _mi_prim_process_info(mi_process_info_t* pinfo)\n{\n  FILETIME ct;\n  FILETIME ut;\n  FILETIME st;\n  FILETIME et;\n  GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut);\n  pinfo->utime = filetime_msecs(&ut);\n  pinfo->stime = filetime_msecs(&st);\n\n  // load psapi on demand\n  if (pGetProcessMemoryInfo == NULL) {\n    HINSTANCE hDll = LoadLibrary(TEXT(\"psapi.dll\"));\n    if (hDll != NULL) {\n      pGetProcessMemoryInfo = (PGetProcessMemoryInfo)(void (*)(void))GetProcAddress(hDll, \"GetProcessMemoryInfo\");\n    }\n  }\n\n  // get process info\n  PROCESS_MEMORY_COUNTERS info; _mi_memzero_var(info);\n  if (pGetProcessMemoryInfo != NULL) {\n    pGetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));\n  }\n  pinfo->current_rss    = (size_t)info.WorkingSetSize;\n  pinfo->peak_rss       = (size_t)info.PeakWorkingSetSize;\n  pinfo->current_commit = (size_t)info.PagefileUsage;\n  pinfo->peak_commit    = (size_t)info.PeakPagefileUsage;\n  pinfo->page_faults    = (size_t)info.PageFaultCount;\n}\n\n//----------------------------------------------------------------\n// Output\n//----------------------------------------------------------------\n\nvoid _mi_prim_out_stderr( const char* msg )\n{\n  // on windows with redirection, the C runtime cannot handle locale dependent output\n  // after the main thread closes so we use direct console output.\n  if (!_mi_preloading()) {\n    // _cputs(msg);  // _cputs cannot be used as it aborts when failing to lock the console\n    static HANDLE hcon = INVALID_HANDLE_VALUE;\n    static bool hconIsConsole = false;\n    if (hcon == INVALID_HANDLE_VALUE) {\n      hcon = GetStdHandle(STD_ERROR_HANDLE);\n      #ifdef MI_HAS_CONSOLE_IO\n      CONSOLE_SCREEN_BUFFER_INFO sbi;\n      hconIsConsole = ((hcon != INVALID_HANDLE_VALUE) && GetConsoleScreenBufferInfo(hcon, &sbi));\n      #endif\n    }\n    const size_t len = _mi_strlen(msg);\n    if (len > 0 && len < UINT32_MAX) {\n      DWORD written = 0;\n      if (hconIsConsole) {\n        #ifdef MI_HAS_CONSOLE_IO\n        WriteConsoleA(hcon, msg, (DWORD)len, &written, NULL);\n        #endif\n      }\n      else if (hcon != INVALID_HANDLE_VALUE) {\n        // use direct write if stderr was redirected\n        WriteFile(hcon, msg, (DWORD)len, &written, NULL);\n      }\n      else {\n        // finally fall back to fputs after all\n        fputs(msg, stderr);\n      }\n    }\n  }\n}\n\n\n//----------------------------------------------------------------\n// Environment\n//----------------------------------------------------------------\n\n// On Windows use GetEnvironmentVariable instead of getenv to work\n// reliably even when this is invoked before the C runtime is initialized.\n// i.e. when `_mi_preloading() == true`.\n// Note: on windows, environment names are not case sensitive.\nbool _mi_prim_getenv(const char* name, char* result, size_t result_size) {\n  result[0] = 0;\n  size_t len = GetEnvironmentVariableA(name, result, (DWORD)result_size);\n  return (len > 0 && len < result_size);\n}\n\n\n//----------------------------------------------------------------\n// Random\n//----------------------------------------------------------------\n\n#if defined(MI_USE_RTLGENRANDOM) // || defined(__cplusplus)\n// We prefer to use BCryptGenRandom instead of (the unofficial) RtlGenRandom but when using\n// dynamic overriding, we observed it can raise an exception when compiled with C++, and\n// sometimes deadlocks when also running under the VS debugger.\n// In contrast, issue #623 implies that on Windows Server 2019 we need to use BCryptGenRandom.\n// To be continued..\n#pragma comment (lib,\"advapi32.lib\")\n#define RtlGenRandom  SystemFunction036\nmi_decl_externc BOOLEAN NTAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength);\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  return (RtlGenRandom(buf, (ULONG)buf_len) != 0);\n}\n\n#else\n\n#ifndef BCRYPT_USE_SYSTEM_PREFERRED_RNG\n#define BCRYPT_USE_SYSTEM_PREFERRED_RNG 0x00000002\n#endif\n\ntypedef LONG (NTAPI *PBCryptGenRandom)(HANDLE, PUCHAR, ULONG, ULONG);\nstatic  PBCryptGenRandom pBCryptGenRandom = NULL;\n\nbool _mi_prim_random_buf(void* buf, size_t buf_len) {\n  if (pBCryptGenRandom == NULL) {\n    HINSTANCE hDll = LoadLibrary(TEXT(\"bcrypt.dll\"));\n    if (hDll != NULL) {\n      pBCryptGenRandom = (PBCryptGenRandom)(void (*)(void))GetProcAddress(hDll, \"BCryptGenRandom\");\n    }\n    if (pBCryptGenRandom == NULL) return false;\n  }\n  return (pBCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)buf_len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) >= 0);\n}\n\n#endif  // MI_USE_RTLGENRANDOM\n\n\n\n//----------------------------------------------------------------\n// Process & Thread Init/Done\n//----------------------------------------------------------------\n\n#if MI_WIN_USE_FIXED_TLS==1\nmi_decl_cache_align size_t _mi_win_tls_offset = 0;\n#endif\n\n//static void mi_debug_out(const char* s) {\n//  HANDLE h = GetStdHandle(STD_ERROR_HANDLE);\n//  WriteConsole(h, s, (DWORD)_mi_strlen(s), NULL, NULL);\n//}\n\nstatic void mi_win_tls_init(DWORD reason) {\n  if (reason==DLL_PROCESS_ATTACH || reason==DLL_THREAD_ATTACH) {\n    #if MI_WIN_USE_FIXED_TLS==1  // we must allocate a TLS slot dynamically\n    if (_mi_win_tls_offset == 0 && reason == DLL_PROCESS_ATTACH) {\n      const DWORD tls_slot = TlsAlloc();  // usually returns slot 1\n      if (tls_slot == TLS_OUT_OF_INDEXES) {\n        _mi_error_message(EFAULT, \"unable to allocate the a TLS slot (rebuild without MI_WIN_USE_FIXED_TLS?)\\n\");\n      }\n      _mi_win_tls_offset = (size_t)tls_slot * sizeof(void*);\n    }\n    #endif\n    #if MI_HAS_TLS_SLOT >= 2  // we must initialize the TLS slot before any allocation\n    if (mi_prim_get_default_heap() == NULL) {\n      _mi_heap_set_default_direct((mi_heap_t*)&_mi_heap_empty);\n      #if MI_DEBUG && MI_WIN_USE_FIXED_TLS==1\n      void* const p = TlsGetValue((DWORD)(_mi_win_tls_offset / sizeof(void*)));\n      mi_assert_internal(p == (void*)&_mi_heap_empty);\n      #endif\n    }\n    #endif\n  }\n}\n\nstatic void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {\n  MI_UNUSED(reserved);\n  MI_UNUSED(module);\n  mi_win_tls_init(reason);\n  if (reason==DLL_PROCESS_ATTACH) {\n    _mi_auto_process_init();\n  }\n  else if (reason==DLL_PROCESS_DETACH) {\n    _mi_auto_process_done();\n  }\n  else if (reason==DLL_THREAD_DETACH && !_mi_is_redirected()) {\n    _mi_thread_done(NULL);\n  }\n}\n\n\n#if defined(MI_SHARED_LIB)\n  #define MI_PRIM_HAS_PROCESS_ATTACH  1\n\n  // Windows DLL: easy to hook into process_init and thread_done\n  BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {\n    mi_win_main((PVOID)inst,reason,reserved);\n    return TRUE;\n  }\n\n  // nothing to do since `_mi_thread_done` is handled through the DLL_THREAD_DETACH event.\n  void _mi_prim_thread_init_auto_done(void) { }\n  void _mi_prim_thread_done_auto_done(void) { }\n  void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n    MI_UNUSED(heap);\n  }\n\n#elif !defined(MI_WIN_USE_FLS)\n  #define MI_PRIM_HAS_PROCESS_ATTACH  1\n\n  static void NTAPI mi_win_main_attach(PVOID module, DWORD reason, LPVOID reserved) {\n    if (reason == DLL_PROCESS_ATTACH || reason == DLL_THREAD_ATTACH) {\n      mi_win_main(module, reason, reserved);\n    }\n  }\n  static void NTAPI mi_win_main_detach(PVOID module, DWORD reason, LPVOID reserved) {\n    if (reason == DLL_PROCESS_DETACH || reason == DLL_THREAD_DETACH) {\n      mi_win_main(module, reason, reserved);\n    }\n  }\n\n  // Set up TLS callbacks in a statically linked library by using special data sections.\n  // See <https://stackoverflow.com/questions/14538159/tls-callback-in-windows>\n  // We use 2 entries to ensure we call attach events before constructors\n  // are called, and detach events after destructors are called.\n  #if defined(__cplusplus)\n  extern \"C\" {\n  #endif\n\n  #if defined(_WIN64)\n    #pragma comment(linker, \"/INCLUDE:_tls_used\")\n    #pragma comment(linker, \"/INCLUDE:_mi_tls_callback_pre\")\n    #pragma comment(linker, \"/INCLUDE:_mi_tls_callback_post\")\n    #pragma const_seg(\".CRT$XLB\")\n    extern const PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[];\n    const PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[] = { &mi_win_main_attach };\n    #pragma const_seg()\n    #pragma const_seg(\".CRT$XLY\")\n    extern const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[];\n    const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_win_main_detach };\n    #pragma const_seg()\n  #else\n    #pragma comment(linker, \"/INCLUDE:__tls_used\")\n    #pragma comment(linker, \"/INCLUDE:__mi_tls_callback_pre\")\n    #pragma comment(linker, \"/INCLUDE:__mi_tls_callback_post\")\n    #pragma data_seg(\".CRT$XLB\")\n    PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[] = { &mi_win_main_attach };\n    #pragma data_seg()\n    #pragma data_seg(\".CRT$XLY\")\n    PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_win_main_detach };\n    #pragma data_seg()\n  #endif\n\n  #if defined(__cplusplus)\n  }\n  #endif\n\n  // nothing to do since `_mi_thread_done` is handled through the DLL_THREAD_DETACH event.\n  void _mi_prim_thread_init_auto_done(void) { }\n  void _mi_prim_thread_done_auto_done(void) { }\n  void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n    MI_UNUSED(heap);\n  }\n\n#else // deprecated: statically linked, use fiber api\n\n  #if defined(_MSC_VER) // on clang/gcc use the constructor attribute (in `src/prim/prim.c`)\n    // MSVC: use data section magic for static libraries\n    // See <https://www.codeguru.com/cpp/misc/misc/applicationcontrol/article.php/c6945/Running-Code-Before-and-After-Main.htm>\n    #define MI_PRIM_HAS_PROCESS_ATTACH 1\n\n    static int mi_process_attach(void) {\n      mi_win_main(NULL,DLL_PROCESS_ATTACH,NULL);\n      atexit(&_mi_auto_process_done);\n      return 0;\n    }\n    typedef int(*mi_crt_callback_t)(void);\n    #if defined(_WIN64)\n      #pragma comment(linker, \"/INCLUDE:_mi_tls_callback\")\n      #pragma section(\".CRT$XIU\", long, read)\n    #else\n      #pragma comment(linker, \"/INCLUDE:__mi_tls_callback\")\n    #endif\n    #pragma data_seg(\".CRT$XIU\")\n    mi_decl_externc mi_crt_callback_t _mi_tls_callback[] = { &mi_process_attach };\n    #pragma data_seg()\n  #endif\n\n  // use the fiber api for calling `_mi_thread_done`.\n  #include <fibersapi.h>\n  #if (_WIN32_WINNT < 0x600)  // before Windows Vista\n  WINBASEAPI DWORD WINAPI FlsAlloc( _In_opt_ PFLS_CALLBACK_FUNCTION lpCallback );\n  WINBASEAPI PVOID WINAPI FlsGetValue( _In_ DWORD dwFlsIndex );\n  WINBASEAPI BOOL  WINAPI FlsSetValue( _In_ DWORD dwFlsIndex, _In_opt_ PVOID lpFlsData );\n  WINBASEAPI BOOL  WINAPI FlsFree(_In_ DWORD dwFlsIndex);\n  #endif\n\n  static DWORD mi_fls_key = (DWORD)(-1);\n\n  static void NTAPI mi_fls_done(PVOID value) {\n    mi_heap_t* heap = (mi_heap_t*)value;\n    if (heap != NULL) {\n      _mi_thread_done(heap);\n      FlsSetValue(mi_fls_key, NULL);  // prevent recursion as _mi_thread_done may set it back to the main heap, issue #672\n    }\n  }\n\n  void _mi_prim_thread_init_auto_done(void) {\n    mi_fls_key = FlsAlloc(&mi_fls_done);\n  }\n\n  void _mi_prim_thread_done_auto_done(void) {\n    // call thread-done on all threads (except the main thread) to prevent\n    // dangling callback pointer if statically linked with a DLL; Issue #208\n    FlsFree(mi_fls_key);\n  }\n\n  void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {\n    mi_assert_internal(mi_fls_key != (DWORD)(-1));\n    FlsSetValue(mi_fls_key, heap);\n  }\n#endif\n\n// ----------------------------------------------------\n// Communicate with the redirection module on Windows\n// ----------------------------------------------------\n#if defined(MI_SHARED_LIB) && !defined(MI_WIN_NOREDIRECT)\n  #define MI_PRIM_HAS_ALLOCATOR_INIT 1\n\n  static bool mi_redirected = false;   // true if malloc redirects to mi_malloc\n\n  bool _mi_is_redirected(void) {\n    return mi_redirected;\n  }\n\n  #ifdef __cplusplus\n  extern \"C\" {\n  #endif\n  mi_decl_export void _mi_redirect_entry(DWORD reason) {\n    // called on redirection; careful as this may be called before DllMain\n    mi_win_tls_init(reason);\n    if (reason == DLL_PROCESS_ATTACH) {\n      mi_redirected = true;\n    }\n    else if (reason == DLL_PROCESS_DETACH) {\n      mi_redirected = false;\n    }\n    else if (reason == DLL_THREAD_DETACH) {\n      _mi_thread_done(NULL);\n    }\n  }\n  __declspec(dllimport) bool mi_cdecl mi_allocator_init(const char** message);\n  __declspec(dllimport) void mi_cdecl mi_allocator_done(void);\n  #ifdef __cplusplus\n  }\n  #endif\n  bool _mi_allocator_init(const char** message) {\n    return mi_allocator_init(message);\n  }\n  void _mi_allocator_done(void) {\n    mi_allocator_done();\n  }\n#endif\n"
  },
  {
    "path": "src/prim/windows/readme.md",
    "content": "## Primitives:\n\n- `prim.c` contains Windows primitives for OS allocation.\n\n## Event Tracing for Windows (ETW)\n\n- `etw.h` is generated from `etw.man` which contains the manifest for mimalloc events.\n  (100 is an allocation, 101 is for a free)\n\n- `etw-mimalloc.wprp` is a profile for the Windows Performance Recorder (WPR).\n  In an admin prompt, you can use:\n  ```\n  > wpr -start src\\prim\\windows\\etw-mimalloc.wprp -filemode\n  > <my mimalloc program>\n  > wpr -stop test.etl\n  ``` \n  and then open `test.etl` in the Windows Performance Analyzer (WPA)."
  },
  {
    "path": "src/random.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/prim.h\"    // _mi_prim_random_buf\n#include <string.h>       // memset\n\n/* ----------------------------------------------------------------------------\nWe use our own PRNG to keep predictable performance of random number generation\nand to avoid implementations that use a lock. We only use the OS provided\nrandom source to initialize the initial seeds. Since we do not need ultimate\nperformance but we do rely on the security (for secret cookies in secure mode)\nwe use a cryptographically secure generator (chacha20).\n-----------------------------------------------------------------------------*/\n\n#define MI_CHACHA_ROUNDS (20)   // perhaps use 12 for better performance?\n\n\n/* ----------------------------------------------------------------------------\nChacha20 implementation as the original algorithm with a 64-bit nonce\nand counter: https://en.wikipedia.org/wiki/Salsa20\nThe input matrix has sixteen 32-bit values:\nPosition  0 to  3: constant key\nPosition  4 to 11: the key\nPosition 12 to 13: the counter.\nPosition 14 to 15: the nonce.\n\nThe implementation uses regular C code which compiles very well on modern compilers.\n(gcc x64 has no register spills, and clang 6+ uses SSE instructions)\n-----------------------------------------------------------------------------*/\n\nstatic inline uint32_t rotl(uint32_t x, uint32_t shift) {\n  return (x << shift) | (x >> (32 - shift));\n}\n\nstatic inline void qround(uint32_t x[16], size_t a, size_t b, size_t c, size_t d) {\n  x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 16);\n  x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 12);\n  x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 8);\n  x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 7);\n}\n\nstatic void chacha_block(mi_random_ctx_t* ctx)\n{\n  // scramble into `x`\n  uint32_t x[16];\n  for (size_t i = 0; i < 16; i++) {\n    x[i] = ctx->input[i];\n  }\n  for (size_t i = 0; i < MI_CHACHA_ROUNDS; i += 2) {\n    qround(x, 0, 4,  8, 12);\n    qround(x, 1, 5,  9, 13);\n    qround(x, 2, 6, 10, 14);\n    qround(x, 3, 7, 11, 15);\n    qround(x, 0, 5, 10, 15);\n    qround(x, 1, 6, 11, 12);\n    qround(x, 2, 7,  8, 13);\n    qround(x, 3, 4,  9, 14);\n  }\n\n  // add scrambled data to the initial state\n  for (size_t i = 0; i < 16; i++) {\n    ctx->output[i] = x[i] + ctx->input[i];\n  }\n  ctx->output_available = 16;\n\n  // increment the counter for the next round\n  ctx->input[12] += 1;\n  if (ctx->input[12] == 0) {\n    ctx->input[13] += 1;\n    if (ctx->input[13] == 0) {  // and keep increasing into the nonce\n      ctx->input[14] += 1;\n    }\n  }\n}\n\nstatic uint32_t chacha_next32(mi_random_ctx_t* ctx) {\n  if (ctx->output_available <= 0) {\n    chacha_block(ctx);\n    ctx->output_available = 16; // (assign again to suppress static analysis warning)\n  }\n  const uint32_t x = ctx->output[16 - ctx->output_available];\n  ctx->output[16 - ctx->output_available] = 0; // reset once the data is handed out\n  ctx->output_available--;\n  return x;\n}\n\nstatic inline uint32_t read32(const uint8_t* p, size_t idx32) {\n  const size_t i = 4*idx32;\n  return ((uint32_t)p[i+0] | (uint32_t)p[i+1] << 8 | (uint32_t)p[i+2] << 16 | (uint32_t)p[i+3] << 24);\n}\n\nstatic void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t nonce)\n{\n  // since we only use chacha for randomness (and not encryption) we\n  // do not _need_ to read 32-bit values as little endian but we do anyways\n  // just for being compatible :-)\n  memset(ctx, 0, sizeof(*ctx));\n  for (size_t i = 0; i < 4; i++) {\n    const uint8_t* sigma = (uint8_t*)\"expand 32-byte k\";\n    ctx->input[i] = read32(sigma,i);\n  }\n  for (size_t i = 0; i < 8; i++) {\n    ctx->input[i + 4] = read32(key,i);\n  }\n  ctx->input[12] = 0;\n  ctx->input[13] = 0;\n  ctx->input[14] = (uint32_t)nonce;\n  ctx->input[15] = (uint32_t)(nonce >> 32);\n}\n\nstatic void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) {\n  memset(ctx_new, 0, sizeof(*ctx_new));\n  _mi_memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input));\n  ctx_new->input[12] = 0;\n  ctx_new->input[13] = 0;\n  ctx_new->input[14] = (uint32_t)nonce;\n  ctx_new->input[15] = (uint32_t)(nonce >> 32);\n  mi_assert_internal(ctx->input[14] != ctx_new->input[14] || ctx->input[15] != ctx_new->input[15]); // do not reuse nonces!\n  chacha_block(ctx_new);\n}\n\n\n/* ----------------------------------------------------------------------------\nRandom interface\n-----------------------------------------------------------------------------*/\n\n#if MI_DEBUG>1\nstatic bool mi_random_is_initialized(mi_random_ctx_t* ctx) {\n  return (ctx != NULL && ctx->input[0] != 0);\n}\n#endif\n\nvoid _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) {\n  mi_assert_internal(mi_random_is_initialized(ctx));\n  mi_assert_internal(ctx != ctx_new);\n  chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new);\n}\n\nuintptr_t _mi_random_next(mi_random_ctx_t* ctx) {\n  mi_assert_internal(mi_random_is_initialized(ctx));\n  uintptr_t r;\n  do {\n    #if MI_INTPTR_SIZE <= 4\n    r = chacha_next32(ctx);\n    #elif MI_INTPTR_SIZE == 8\n    r = (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));\n    #else\n    # error \"define mi_random_next for this platform\"\n    #endif\n  } while (r==0);\n  return r;\n}\n\n\n/* ----------------------------------------------------------------------------\nTo initialize a fresh random context.\nIf we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR.\n-----------------------------------------------------------------------------*/\n\nuintptr_t _mi_os_random_weak(uintptr_t extra_seed) {\n  uintptr_t x = (uintptr_t)&_mi_os_random_weak ^ extra_seed; // ASLR makes the address random\n  x ^= _mi_prim_clock_now();  \n  // and do a few randomization steps\n  uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1;\n  for (uintptr_t i = 0; i < max || x==0; i++, x++) {\n    x = _mi_random_shuffle(x);\n  }\n  mi_assert_internal(x != 0);\n  return x;\n}\n\nstatic void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) {\n  uint8_t key[32];\n  if (use_weak || !_mi_prim_random_buf(key, sizeof(key))) {\n    // if we fail to get random data from the OS, we fall back to a\n    // weak random source based on the current time\n    #if !defined(__wasi__)\n    if (!use_weak) { _mi_warning_message(\"unable to use secure randomness\\n\"); }\n    #endif\n    uintptr_t x = _mi_os_random_weak(0);\n    for (size_t i = 0; i < 8; i++, x++) {  // key is eight 32-bit words.\n      x = _mi_random_shuffle(x);\n      ((uint32_t*)key)[i] = (uint32_t)x;\n    }\n    ctx->weak = true;\n  }\n  else {\n    ctx->weak = false;\n  }\n  chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ );\n}\n\nvoid _mi_random_init(mi_random_ctx_t* ctx) {\n  mi_random_init_ex(ctx, false);\n}\n\nvoid _mi_random_init_weak(mi_random_ctx_t * ctx) {\n  mi_random_init_ex(ctx, true);\n}\n\nvoid _mi_random_reinit_if_weak(mi_random_ctx_t * ctx) {\n  if (ctx->weak) {\n    _mi_random_init(ctx);\n  }\n}\n\n/* --------------------------------------------------------\ntest vectors from <https://tools.ietf.org/html/rfc8439>\n----------------------------------------------------------- */\n/*\nstatic bool array_equals(uint32_t* x, uint32_t* y, size_t n) {\n  for (size_t i = 0; i < n; i++) {\n    if (x[i] != y[i]) return false;\n  }\n  return true;\n}\nstatic void chacha_test(void)\n{\n  uint32_t x[4] = { 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567 };\n  uint32_t x_out[4] = { 0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb };\n  qround(x, 0, 1, 2, 3);\n  mi_assert_internal(array_equals(x, x_out, 4));\n\n  uint32_t y[16] = {\n       0x879531e0,  0xc5ecf37d,  0x516461b1,  0xc9a62f8a,\n       0x44c20ef3,  0x3390af7f,  0xd9fc690b,  0x2a5f714c,\n       0x53372767,  0xb00a5631,  0x974c541a,  0x359e9963,\n       0x5c971061,  0x3d631689,  0x2098d9d6,  0x91dbd320 };\n  uint32_t y_out[16] = {\n       0x879531e0,  0xc5ecf37d,  0xbdb886dc,  0xc9a62f8a,\n       0x44c20ef3,  0x3390af7f,  0xd9fc690b,  0xcfacafd2,\n       0xe46bea80,  0xb00a5631,  0x974c541a,  0x359e9963,\n       0x5c971061,  0xccc07c79,  0x2098d9d6,  0x91dbd320 };\n  qround(y, 2, 7, 8, 13);\n  mi_assert_internal(array_equals(y, y_out, 16));\n\n  mi_random_ctx_t r = {\n    { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,\n      0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,\n      0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c,\n      0x00000001, 0x09000000, 0x4a000000, 0x00000000 },\n    {0},\n    0\n  };\n  uint32_t r_out[16] = {\n       0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3,\n       0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3,\n       0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9,\n       0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 };\n  chacha_block(&r);\n  mi_assert_internal(array_equals(r.output, r_out, 16));\n}\n*/\n"
  },
  {
    "path": "src/segment-map.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2019-2023, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* -----------------------------------------------------------\n  The following functions are to reliably find the segment or\n  block that encompasses any pointer p (or NULL if it is not\n  in any of our segments).\n  We maintain a bitmap of all memory with 1 bit per MI_SEGMENT_SIZE (64MiB)\n  set to 1 if it contains the segment meta data.\n----------------------------------------------------------- */\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n\n// Reduce total address space to reduce .bss  (due to the `mi_segment_map`)\n#if (MI_INTPTR_SIZE > 4) && MI_TRACK_ASAN\n#define MI_SEGMENT_MAP_MAX_ADDRESS    (128*1024ULL*MI_GiB)  // 128 TiB  (see issue #881)\n#elif (MI_INTPTR_SIZE > 4)\n#define MI_SEGMENT_MAP_MAX_ADDRESS    (48*1024ULL*MI_GiB)   // 48 TiB\n#else\n#define MI_SEGMENT_MAP_MAX_ADDRESS    (UINT32_MAX)\n#endif\n\n#define MI_SEGMENT_MAP_PART_SIZE      (MI_INTPTR_SIZE*MI_KiB - 128)      // 128 > sizeof(mi_memid_t) ! \n#define MI_SEGMENT_MAP_PART_BITS      (8*MI_SEGMENT_MAP_PART_SIZE)\n#define MI_SEGMENT_MAP_PART_ENTRIES   (MI_SEGMENT_MAP_PART_SIZE / MI_INTPTR_SIZE)\n#define MI_SEGMENT_MAP_PART_BIT_SPAN  (MI_SEGMENT_ALIGN)                 // memory area covered by 1 bit\n\n#if (MI_SEGMENT_MAP_PART_BITS < (MI_SEGMENT_MAP_MAX_ADDRESS / MI_SEGMENT_MAP_PART_BIT_SPAN)) // prevent overflow on 32-bit (issue #1017)\n#define MI_SEGMENT_MAP_PART_SPAN      (MI_SEGMENT_MAP_PART_BITS * MI_SEGMENT_MAP_PART_BIT_SPAN)\n#else\n#define MI_SEGMENT_MAP_PART_SPAN      MI_SEGMENT_MAP_MAX_ADDRESS\n#endif\n\n#define MI_SEGMENT_MAP_MAX_PARTS      ((MI_SEGMENT_MAP_MAX_ADDRESS / MI_SEGMENT_MAP_PART_SPAN) + 1)\n\n// A part of the segment map.\ntypedef struct mi_segmap_part_s {\n  mi_memid_t memid;\n  _Atomic(uintptr_t) map[MI_SEGMENT_MAP_PART_ENTRIES];\n} mi_segmap_part_t;\n\n// Allocate parts on-demand to reduce .bss footprint\nstatic _Atomic(mi_segmap_part_t*) mi_segment_map[MI_SEGMENT_MAP_MAX_PARTS]; // = { NULL, .. }\n\nstatic mi_segmap_part_t* mi_segment_map_index_of(const mi_segment_t* segment, bool create_on_demand, size_t* idx, size_t* bitidx) {\n  // note: segment can be invalid or NULL.\n  mi_assert_internal(_mi_ptr_segment(segment + 1) == segment); // is it aligned on MI_SEGMENT_SIZE?\n  *idx = 0;\n  *bitidx = 0;  \n  if ((uintptr_t)segment >= MI_SEGMENT_MAP_MAX_ADDRESS) return NULL;\n  const uintptr_t segindex = ((uintptr_t)segment) / MI_SEGMENT_MAP_PART_SPAN;\n  if (segindex >= MI_SEGMENT_MAP_MAX_PARTS) return NULL;\n  mi_segmap_part_t* part = mi_atomic_load_ptr_relaxed(mi_segmap_part_t, &mi_segment_map[segindex]);\n\n  // allocate on demand to reduce .bss footprint\n  if mi_unlikely(part == NULL) {\n    if (!create_on_demand) return NULL;\n    mi_memid_t memid;\n    part = (mi_segmap_part_t*)_mi_os_zalloc(sizeof(mi_segmap_part_t), &memid);\n    if (part == NULL) return NULL;\n    part->memid = memid;\n    mi_segmap_part_t* expected = NULL;\n    if (!mi_atomic_cas_ptr_strong_release(mi_segmap_part_t, &mi_segment_map[segindex], &expected, part)) {\n      _mi_os_free(part, sizeof(mi_segmap_part_t), memid);\n      part = expected;\n      if (part == NULL) return NULL;\n    }\n  }\n  mi_assert(part != NULL);\n  const uintptr_t offset = ((uintptr_t)segment) % MI_SEGMENT_MAP_PART_SPAN;\n  const uintptr_t bitofs = offset / MI_SEGMENT_MAP_PART_BIT_SPAN;\n  *idx = bitofs / MI_INTPTR_BITS;\n  *bitidx = bitofs % MI_INTPTR_BITS;\n  return part;\n}\n\nvoid _mi_segment_map_allocated_at(const mi_segment_t* segment) {\n  if (segment->memid.memkind == MI_MEM_ARENA) return; // we lookup segments first in the arena's and don't need the segment map\n  size_t index;\n  size_t bitidx;\n  mi_segmap_part_t* part = mi_segment_map_index_of(segment, true /* alloc map if needed */, &index, &bitidx);\n  if (part == NULL) return; // outside our address range..\n  uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);\n  uintptr_t newmask;\n  do {\n    newmask = (mask | ((uintptr_t)1 << bitidx));\n  } while (!mi_atomic_cas_weak_release(&part->map[index], &mask, newmask));\n}\n\nvoid _mi_segment_map_freed_at(const mi_segment_t* segment) {\n  if (segment->memid.memkind == MI_MEM_ARENA) return;\n  size_t index;\n  size_t bitidx;\n  mi_segmap_part_t* part = mi_segment_map_index_of(segment, false /* don't alloc if not present */, &index, &bitidx);\n  if (part == NULL) return; // outside our address range..\n  uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);\n  uintptr_t newmask;\n  do {\n    newmask = (mask & ~((uintptr_t)1 << bitidx));\n  } while (!mi_atomic_cas_weak_release(&part->map[index], &mask, newmask));\n}\n\n// Determine the segment belonging to a pointer or NULL if it is not in a valid segment.\nstatic mi_segment_t* _mi_segment_of(const void* p) {\n  if (p == NULL) return NULL;\n  mi_segment_t* segment = _mi_ptr_segment(p);  // segment can be NULL  \n  size_t index;\n  size_t bitidx;\n  mi_segmap_part_t* part = mi_segment_map_index_of(segment, false /* dont alloc if not present */, &index, &bitidx);\n  if (part == NULL) return NULL;  \n  const uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);\n  if mi_likely((mask & ((uintptr_t)1 << bitidx)) != 0) {\n    bool cookie_ok = (_mi_ptr_cookie(segment) == segment->cookie);\n    mi_assert_internal(cookie_ok); MI_UNUSED(cookie_ok);\n    return segment; // yes, allocated by us\n  }\n  return NULL;\n}\n\n// Is this a valid pointer in our heap?\nstatic bool mi_is_valid_pointer(const void* p) {\n  // first check if it is in an arena, then check if it is OS allocated\n  return (_mi_arena_contains(p) || _mi_segment_of(p) != NULL);\n}\n\nmi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {\n  return mi_is_valid_pointer(p);\n}\n\nvoid _mi_segment_map_unsafe_destroy(void) {\n  for (size_t i = 0; i < MI_SEGMENT_MAP_MAX_PARTS; i++) {\n    mi_segmap_part_t* part = mi_atomic_exchange_ptr_relaxed(mi_segmap_part_t, &mi_segment_map[i], NULL);\n    if (part != NULL) {\n      _mi_os_free(part, sizeof(mi_segmap_part_t), part->memid);\n    }\n  }\n}\n"
  },
  {
    "path": "src/segment.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2024, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n\n#include <string.h>  // memset\n#include <stdio.h>\n\n// -------------------------------------------------------------------\n// Segments\n// mimalloc pages reside in segments. See `mi_segment_valid` for invariants.\n// -------------------------------------------------------------------\n\n\nstatic void mi_segment_try_purge(mi_segment_t* segment, bool force);\n\n\n// -------------------------------------------------------------------\n// commit mask\n// -------------------------------------------------------------------\n\nstatic bool mi_commit_mask_all_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    if ((commit->mask[i] & cm->mask[i]) != cm->mask[i]) return false;\n  }\n  return true;\n}\n\nstatic bool mi_commit_mask_any_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    if ((commit->mask[i] & cm->mask[i]) != 0) return true;\n  }\n  return false;\n}\n\nstatic void mi_commit_mask_create_intersect(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm, mi_commit_mask_t* res) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    res->mask[i] = (commit->mask[i] & cm->mask[i]);\n  }\n}\n\nstatic void mi_commit_mask_clear(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    res->mask[i] &= ~(cm->mask[i]);\n  }\n}\n\nstatic void mi_commit_mask_set(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    res->mask[i] |= cm->mask[i];\n  }\n}\n\nstatic void mi_commit_mask_create(size_t bitidx, size_t bitcount, mi_commit_mask_t* cm) {\n  mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);\n  mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);\n  if (bitcount == MI_COMMIT_MASK_BITS) {\n    mi_assert_internal(bitidx==0);\n    mi_commit_mask_create_full(cm);\n  }\n  else if (bitcount == 0) {\n    mi_commit_mask_create_empty(cm);\n  }\n  else {\n    mi_commit_mask_create_empty(cm);\n    size_t i = bitidx / MI_COMMIT_MASK_FIELD_BITS;\n    size_t ofs = bitidx % MI_COMMIT_MASK_FIELD_BITS;\n    while (bitcount > 0) {\n      mi_assert_internal(i < MI_COMMIT_MASK_FIELD_COUNT);\n      size_t avail = MI_COMMIT_MASK_FIELD_BITS - ofs;\n      size_t count = (bitcount > avail ? avail : bitcount);\n      size_t mask = (count >= MI_COMMIT_MASK_FIELD_BITS ? ~((size_t)0) : (((size_t)1 << count) - 1) << ofs);\n      cm->mask[i] = mask;\n      bitcount -= count;\n      ofs = 0;\n      i++;\n    }\n  }\n}\n\nsize_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total) {\n  mi_assert_internal((total%MI_COMMIT_MASK_BITS)==0);\n  size_t count = 0;\n  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {\n    size_t mask = cm->mask[i];\n    if (~mask == 0) {\n      count += MI_COMMIT_MASK_FIELD_BITS;\n    }\n    else {\n      for (; mask != 0; mask >>= 1) {  // todo: use popcount\n        if ((mask&1)!=0) count++;\n      }\n    }\n  }\n  // we use total since for huge segments each commit bit may represent a larger size\n  return ((total / MI_COMMIT_MASK_BITS) * count);\n}\n\n\nsize_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx) {\n  size_t i = (*idx) / MI_COMMIT_MASK_FIELD_BITS;\n  size_t ofs = (*idx) % MI_COMMIT_MASK_FIELD_BITS;\n  size_t mask = 0;\n  // find first ones\n  while (i < MI_COMMIT_MASK_FIELD_COUNT) {\n    mask = cm->mask[i];\n    mask >>= ofs;\n    if (mask != 0) {\n      while ((mask&1) == 0) {\n        mask >>= 1;\n        ofs++;\n      }\n      break;\n    }\n    i++;\n    ofs = 0;\n  }\n  if (i >= MI_COMMIT_MASK_FIELD_COUNT) {\n    // not found\n    *idx = MI_COMMIT_MASK_BITS;\n    return 0;\n  }\n  else {\n    // found, count ones\n    size_t count = 0;\n    *idx = (i*MI_COMMIT_MASK_FIELD_BITS) + ofs;\n    do {\n      mi_assert_internal(ofs < MI_COMMIT_MASK_FIELD_BITS && (mask&1) == 1);\n      do {\n        count++;\n        mask >>= 1;\n      } while ((mask&1) == 1);\n      if ((((*idx + count) % MI_COMMIT_MASK_FIELD_BITS) == 0)) {\n        i++;\n        if (i >= MI_COMMIT_MASK_FIELD_COUNT) break;\n        mask = cm->mask[i];\n        ofs = 0;\n      }\n    } while ((mask&1) == 1);\n    mi_assert_internal(count > 0);\n    return count;\n  }\n}\n\n\n/* --------------------------------------------------------------------------------\n  Segment allocation\n  We allocate pages inside bigger \"segments\" (32 MiB on 64-bit). This is to avoid\n  splitting VMA's on Linux and reduce fragmentation on other OS's.\n  Each thread owns its own segments.\n\n  Currently we have:\n  - small pages (64KiB)\n  - medium pages (512KiB)\n  - large pages (4MiB),\n  - huge segments have 1 page in one segment that can be larger than `MI_SEGMENT_SIZE`.\n    it is used for blocks `> MI_LARGE_OBJ_SIZE_MAX` or with alignment `> MI_BLOCK_ALIGNMENT_MAX`.\n\n  The memory for a segment is usually committed on demand.\n  (i.e. we are careful to not touch the memory until we actually allocate a block there)\n\n  If a  thread ends, it \"abandons\" pages that still contain live blocks.\n  Such segments are abandoned and these can be reclaimed by still running threads,\n  (much like work-stealing).\n-------------------------------------------------------------------------------- */\n\n\n/* -----------------------------------------------------------\n   Slices\n----------------------------------------------------------- */\n\n\nstatic const mi_slice_t* mi_segment_slices_end(const mi_segment_t* segment) {\n  return &segment->slices[segment->slice_entries];\n}\n\nstatic uint8_t* mi_slice_start(const mi_slice_t* slice) {\n  mi_segment_t* segment = _mi_ptr_segment(slice);\n  mi_assert_internal(slice >= segment->slices && slice < mi_segment_slices_end(segment));\n  return ((uint8_t*)segment + ((slice - segment->slices)*MI_SEGMENT_SLICE_SIZE));\n}\n\n\n/* -----------------------------------------------------------\n   Bins\n----------------------------------------------------------- */\n// Use bit scan forward to quickly find the first zero bit if it is available\n\nstatic inline size_t mi_slice_bin8(size_t slice_count) {\n  if (slice_count<=1) return slice_count;\n  mi_assert_internal(slice_count <= MI_SLICES_PER_SEGMENT);\n  slice_count--;\n  size_t s = mi_bsr(slice_count);  // slice_count > 1\n  if (s <= 2) return slice_count + 1;\n  size_t bin = ((s << 2) | ((slice_count >> (s - 2))&0x03)) - 4;\n  return bin;\n}\n\nstatic inline size_t mi_slice_bin(size_t slice_count) {\n  mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_SEGMENT_SIZE);\n  mi_assert_internal(mi_slice_bin8(MI_SLICES_PER_SEGMENT) <= MI_SEGMENT_BIN_MAX);\n  size_t bin = mi_slice_bin8(slice_count);\n  mi_assert_internal(bin <= MI_SEGMENT_BIN_MAX);\n  return bin;\n}\n\nstatic inline size_t mi_slice_index(const mi_slice_t* slice) {\n  mi_segment_t* segment = _mi_ptr_segment(slice);\n  ptrdiff_t index = slice - segment->slices;\n  mi_assert_internal(index >= 0 && index < (ptrdiff_t)segment->slice_entries);\n  return index;\n}\n\n\n/* -----------------------------------------------------------\n   Slice span queues\n----------------------------------------------------------- */\n\nstatic void mi_span_queue_push(mi_span_queue_t* sq, mi_slice_t* slice) {\n  // todo: or push to the end?\n  mi_assert_internal(slice->prev == NULL && slice->next==NULL);\n  slice->prev = NULL; // paranoia\n  slice->next = sq->first;\n  sq->first = slice;\n  if (slice->next != NULL) slice->next->prev = slice;\n                     else sq->last = slice;\n  slice->block_size = 0; // free\n}\n\nstatic mi_span_queue_t* mi_span_queue_for(size_t slice_count, mi_segments_tld_t* tld) {\n  size_t bin = mi_slice_bin(slice_count);\n  mi_span_queue_t* sq = &tld->spans[bin];\n  mi_assert_internal(sq->slice_count >= slice_count);\n  return sq;\n}\n\nstatic void mi_span_queue_delete(mi_span_queue_t* sq, mi_slice_t* slice) {\n  mi_assert_internal(slice->block_size==0 && slice->slice_count>0 && slice->slice_offset==0);\n  // should work too if the queue does not contain slice (which can happen during reclaim)\n  if (slice->prev != NULL) slice->prev->next = slice->next;\n  if (slice == sq->first) sq->first = slice->next;\n  if (slice->next != NULL) slice->next->prev = slice->prev;\n  if (slice == sq->last) sq->last = slice->prev;\n  slice->prev = NULL;\n  slice->next = NULL;\n  slice->block_size = 1; // no more free\n}\n\n\n/* -----------------------------------------------------------\n Invariant checking\n----------------------------------------------------------- */\n\nstatic bool mi_slice_is_used(const mi_slice_t* slice) {\n  return (slice->block_size > 0);\n}\n\n\n#if (MI_DEBUG>=3)\nstatic bool mi_span_queue_contains(mi_span_queue_t* sq, mi_slice_t* slice) {\n  for (mi_slice_t* s = sq->first; s != NULL; s = s->next) {\n    if (s==slice) return true;\n  }\n  return false;\n}\n\nstatic bool mi_segment_is_valid(mi_segment_t* segment, mi_segments_tld_t* tld) {\n  mi_assert_internal(segment != NULL);\n  mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie);\n  mi_assert_internal(segment->abandoned <= segment->used);\n  mi_assert_internal(segment->thread_id == 0 || segment->thread_id == _mi_thread_id());\n  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); // can only decommit committed blocks\n  //mi_assert_internal(segment->segment_info_size % MI_SEGMENT_SLICE_SIZE == 0);\n  mi_slice_t* slice = &segment->slices[0];\n  const mi_slice_t* end = mi_segment_slices_end(segment);\n  size_t used_count = 0;\n  mi_span_queue_t* sq;\n  while(slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    size_t index = mi_slice_index(slice);\n    size_t maxindex = (index + slice->slice_count >= segment->slice_entries ? segment->slice_entries : index + slice->slice_count) - 1;\n    if (mi_slice_is_used(slice)) { // a page in use, we need at least MAX_SLICE_OFFSET_COUNT valid back offsets\n      used_count++;\n      mi_assert_internal(slice->is_huge == (segment->kind == MI_SEGMENT_HUGE));\n      for (size_t i = 0; i <= MI_MAX_SLICE_OFFSET_COUNT && index + i <= maxindex; i++) {\n        mi_assert_internal(segment->slices[index + i].slice_offset == i*sizeof(mi_slice_t));\n        mi_assert_internal(i==0 || segment->slices[index + i].slice_count == 0);\n        mi_assert_internal(i==0 || segment->slices[index + i].block_size == 1);\n      }\n      // and the last entry as well (for coalescing)\n      const mi_slice_t* last = slice + slice->slice_count - 1;\n      if (last > slice && last < mi_segment_slices_end(segment)) {\n        mi_assert_internal(last->slice_offset == (slice->slice_count-1)*sizeof(mi_slice_t));\n        mi_assert_internal(last->slice_count == 0);\n        mi_assert_internal(last->block_size == 1);\n      }\n    }\n    else {  // free range of slices; only last slice needs a valid back offset\n      mi_slice_t* last = &segment->slices[maxindex];\n      if (segment->kind != MI_SEGMENT_HUGE || slice->slice_count <= (segment->slice_entries - segment->segment_info_slices)) {\n        mi_assert_internal((uint8_t*)slice == (uint8_t*)last - last->slice_offset);\n      }\n      mi_assert_internal(slice == last || last->slice_count == 0 );\n      mi_assert_internal(last->block_size == 0 || (segment->kind==MI_SEGMENT_HUGE && last->block_size==1));\n      if (segment->kind != MI_SEGMENT_HUGE && segment->thread_id != 0) { // segment is not huge or abandoned\n        sq = mi_span_queue_for(slice->slice_count,tld);\n        mi_assert_internal(mi_span_queue_contains(sq,slice));\n      }\n    }\n    slice = &segment->slices[maxindex+1];\n  }\n  mi_assert_internal(slice == end);\n  mi_assert_internal(used_count == segment->used + 1);\n  return true;\n}\n#endif\n\n/* -----------------------------------------------------------\n Segment size calculations\n----------------------------------------------------------- */\n\nstatic size_t mi_segment_info_size(mi_segment_t* segment) {\n  return segment->segment_info_slices * MI_SEGMENT_SLICE_SIZE;\n}\n\nstatic uint8_t* _mi_segment_page_start_from_slice(const mi_segment_t* segment, const mi_slice_t* slice, size_t block_size, size_t* page_size)\n{\n  const ptrdiff_t idx = slice - segment->slices;\n  const size_t psize = (size_t)slice->slice_count * MI_SEGMENT_SLICE_SIZE;\n  uint8_t* const pstart = (uint8_t*)segment + (idx*MI_SEGMENT_SLICE_SIZE);\n  // make the start not OS page aligned for smaller blocks to avoid page/cache effects\n  // note: the offset must always be a block_size multiple since we assume small allocations\n  // are aligned (see `mi_heap_malloc_aligned`).\n  size_t start_offset = 0;\n  if (block_size > 0 && block_size <= MI_MAX_ALIGN_GUARANTEE) {\n    // for small objects, ensure the page start is aligned with the block size (PR#66 by kickunderscore)\n    const size_t adjust = block_size - ((uintptr_t)pstart % block_size);\n    if (adjust < block_size && psize >= block_size + adjust) {\n      start_offset += adjust;\n    }\n  }\n  if (block_size >= MI_INTPTR_SIZE) {\n    if (block_size <= 64) { start_offset += 3*block_size; }\n    else if (block_size <= 512) { start_offset += block_size; }\n  }\n  start_offset = _mi_align_up(start_offset, MI_MAX_ALIGN_SIZE);\n  mi_assert_internal(_mi_is_aligned(pstart + start_offset, MI_MAX_ALIGN_SIZE));\n  mi_assert_internal(block_size == 0 || block_size > MI_MAX_ALIGN_GUARANTEE || _mi_is_aligned(pstart + start_offset,block_size));\n  if (page_size != NULL) { *page_size = psize - start_offset; }\n  return (pstart + start_offset);\n}\n\n// Start of the page available memory; can be used on uninitialized pages\nuint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size)\n{\n  const mi_slice_t* slice = mi_page_to_slice((mi_page_t*)page);\n  uint8_t* p = _mi_segment_page_start_from_slice(segment, slice, mi_page_block_size(page), page_size);\n  mi_assert_internal(mi_page_block_size(page) > 0 || _mi_ptr_page(p) == page);\n  mi_assert_internal(_mi_ptr_segment(p) == segment);\n  return p;\n}\n\n\nstatic size_t mi_segment_calculate_slices(size_t required, size_t* info_slices) {\n  size_t page_size = _mi_os_page_size();\n  size_t isize     = _mi_align_up(sizeof(mi_segment_t), page_size);\n  size_t guardsize = 0;\n\n  if (MI_SECURE>0) {\n    // in secure mode, we set up a protected page in between the segment info\n    // and the page data (and one at the end of the segment)\n    guardsize = page_size;\n    if (required > 0) {\n      required = _mi_align_up(required, MI_SEGMENT_SLICE_SIZE) + page_size;\n    }\n  }\n\n  isize = _mi_align_up(isize + guardsize, MI_SEGMENT_SLICE_SIZE);\n  if (info_slices != NULL) *info_slices = isize / MI_SEGMENT_SLICE_SIZE;\n  size_t segment_size = (required==0 ? MI_SEGMENT_SIZE : _mi_align_up( required + isize + guardsize, MI_SEGMENT_SLICE_SIZE) );\n  mi_assert_internal(segment_size % MI_SEGMENT_SLICE_SIZE == 0);\n  return (segment_size / MI_SEGMENT_SLICE_SIZE);\n}\n\n\n/* ----------------------------------------------------------------------------\nSegment caches\nWe keep a small segment cache per thread to increase local\nreuse and avoid setting/clearing guard pages in secure mode.\n------------------------------------------------------------------------------- */\n\nstatic void mi_segments_track_size(long segment_size, mi_segments_tld_t* tld) {\n  if (segment_size>=0) _mi_stat_increase(&tld->stats->segments,1);\n                  else _mi_stat_decrease(&tld->stats->segments,1);\n  tld->count += (segment_size >= 0 ? 1 : -1);\n  if (tld->count > tld->peak_count) tld->peak_count = tld->count;\n  tld->current_size += segment_size;\n  if (tld->current_size > tld->peak_size) tld->peak_size = tld->current_size;\n}\n\nstatic void mi_segment_os_free(mi_segment_t* segment, mi_segments_tld_t* tld) {\n  segment->thread_id = 0;\n  _mi_segment_map_freed_at(segment);\n  mi_segments_track_size(-((long)mi_segment_size(segment)),tld);\n  if (segment->was_reclaimed) {\n    tld->reclaim_count--;\n    segment->was_reclaimed = false;\n  }\n  if (MI_SECURE>0) {\n    // _mi_os_unprotect(segment, mi_segment_size(segment)); // ensure no more guard pages are set\n    // unprotect the guard pages; we cannot just unprotect the whole segment size as part may be decommitted\n    size_t os_pagesize = _mi_os_page_size();\n    _mi_os_unprotect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);\n    uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;\n    _mi_os_unprotect(end, os_pagesize);\n  }\n\n  // purge delayed decommits now? (no, leave it to the arena)\n  // mi_segment_try_purge(segment,true,tld->stats);\n\n  const size_t size = mi_segment_size(segment);\n  const size_t csize = _mi_commit_mask_committed_size(&segment->commit_mask, size);\n\n  _mi_arena_free(segment, mi_segment_size(segment), csize, segment->memid);\n}\n\n/* -----------------------------------------------------------\n   Commit/Decommit ranges\n----------------------------------------------------------- */\n\nstatic void mi_segment_commit_mask(mi_segment_t* segment, bool conservative, uint8_t* p, size_t size, uint8_t** start_p, size_t* full_size, mi_commit_mask_t* cm) {\n  mi_assert_internal(_mi_ptr_segment(p + 1) == segment);\n  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);\n  mi_commit_mask_create_empty(cm);\n  if (size == 0 || size > MI_SEGMENT_SIZE || segment->kind == MI_SEGMENT_HUGE) return;\n  const size_t segstart = mi_segment_info_size(segment);\n  const size_t segsize = mi_segment_size(segment);\n  if (p >= (uint8_t*)segment + segsize) return;\n\n  size_t pstart = (p - (uint8_t*)segment);\n  mi_assert_internal(pstart + size <= segsize);\n\n  size_t start;\n  size_t end;\n  if (conservative) {\n    // decommit conservative\n    start = _mi_align_up(pstart, MI_COMMIT_SIZE);\n    end   = _mi_align_down(pstart + size, MI_COMMIT_SIZE);\n    mi_assert_internal(start >= segstart);\n    mi_assert_internal(end <= segsize);\n  }\n  else {\n    // commit liberal\n    start = _mi_align_down(pstart, MI_MINIMAL_COMMIT_SIZE);\n    end   = _mi_align_up(pstart + size, MI_MINIMAL_COMMIT_SIZE);\n  }\n  if (pstart >= segstart && start < segstart) {  // note: the mask is also calculated for an initial commit of the info area\n    start = segstart;\n  }\n  if (end > segsize) {\n    end = segsize;\n  }\n\n  mi_assert_internal(start <= pstart && (pstart + size) <= end);\n  mi_assert_internal(start % MI_COMMIT_SIZE==0 && end % MI_COMMIT_SIZE == 0);\n  *start_p   = (uint8_t*)segment + start;\n  *full_size = (end > start ? end - start : 0);\n  if (*full_size == 0) return;\n\n  size_t bitidx = start / MI_COMMIT_SIZE;\n  mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);\n\n  size_t bitcount = *full_size / MI_COMMIT_SIZE; // can be 0\n  if (bitidx + bitcount > MI_COMMIT_MASK_BITS) {\n    _mi_warning_message(\"commit mask overflow: idx=%zu count=%zu start=%zx end=%zx p=0x%p size=%zu fullsize=%zu\\n\", bitidx, bitcount, start, end, p, size, *full_size);\n  }\n  mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);\n  mi_commit_mask_create(bitidx, bitcount, cm);\n}\n\nstatic bool mi_segment_commit(mi_segment_t* segment, uint8_t* p, size_t size) {\n  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));\n\n  // commit liberal\n  uint8_t* start = NULL;\n  size_t   full_size = 0;\n  mi_commit_mask_t mask;\n  mi_segment_commit_mask(segment, false /* conservative? */, p, size, &start, &full_size, &mask);\n  if (mi_commit_mask_is_empty(&mask) || full_size == 0) return true;\n\n  if (!mi_commit_mask_all_set(&segment->commit_mask, &mask)) {\n    // committing\n    bool is_zero = false;\n    mi_commit_mask_t cmask;\n    mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);\n    _mi_stat_decrease(&_mi_stats_main.committed, _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for overlap\n    if (!_mi_os_commit(start, full_size, &is_zero)) return false;\n    mi_commit_mask_set(&segment->commit_mask, &mask);\n  }\n\n  // increase purge expiration when using part of delayed purges -- we assume more allocations are coming soon.\n  if (mi_commit_mask_any_set(&segment->purge_mask, &mask)) {\n    segment->purge_expire = _mi_clock_now() + mi_option_get(mi_option_purge_delay);\n  }\n\n  // always clear any delayed purges in our range (as they are either committed now)\n  mi_commit_mask_clear(&segment->purge_mask, &mask);\n  return true;\n}\n\nstatic bool mi_segment_ensure_committed(mi_segment_t* segment, uint8_t* p, size_t size) {\n  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));\n  // note: assumes commit_mask is always full for huge segments as otherwise the commit mask bits can overflow\n  if (mi_commit_mask_is_full(&segment->commit_mask) && mi_commit_mask_is_empty(&segment->purge_mask)) return true; // fully committed\n  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);\n  return mi_segment_commit(segment, p, size);\n}\n\nstatic bool mi_segment_purge(mi_segment_t* segment, uint8_t* p, size_t size) {\n  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));\n  if (!segment->allow_purge) return true;\n\n  // purge conservative\n  uint8_t* start = NULL;\n  size_t   full_size = 0;\n  mi_commit_mask_t mask;\n  mi_segment_commit_mask(segment, true /* conservative? */, p, size, &start, &full_size, &mask);\n  if (mi_commit_mask_is_empty(&mask) || full_size==0) return true;\n\n  if (mi_commit_mask_any_set(&segment->commit_mask, &mask)) {\n    // purging\n    mi_assert_internal((void*)start != (void*)segment);\n    mi_assert_internal(segment->allow_decommit);\n    const bool decommitted = _mi_os_purge(start, full_size);  // reset or decommit\n    if (decommitted) {\n      mi_commit_mask_t cmask;\n      mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);\n      _mi_stat_increase(&_mi_stats_main.committed, full_size - _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for double counting\n      mi_commit_mask_clear(&segment->commit_mask, &mask);\n    }\n  }\n\n  // always clear any scheduled purges in our range\n  mi_commit_mask_clear(&segment->purge_mask, &mask);\n  return true;\n}\n\nstatic void mi_segment_schedule_purge(mi_segment_t* segment, uint8_t* p, size_t size) {\n  if (!segment->allow_purge) return;\n\n  if (mi_option_get(mi_option_purge_delay) == 0) {\n    mi_segment_purge(segment, p, size);\n  }\n  else {\n    // register for future purge in the purge mask\n    uint8_t* start = NULL;\n    size_t   full_size = 0;\n    mi_commit_mask_t mask;\n    mi_segment_commit_mask(segment, true /*conservative*/, p, size, &start, &full_size, &mask);\n    if (mi_commit_mask_is_empty(&mask) || full_size==0) return;\n\n    // update delayed commit\n    mi_assert_internal(segment->purge_expire > 0 || mi_commit_mask_is_empty(&segment->purge_mask));\n    mi_commit_mask_t cmask;\n    mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);  // only purge what is committed; span_free may try to decommit more\n    mi_commit_mask_set(&segment->purge_mask, &cmask);\n    mi_msecs_t now = _mi_clock_now();\n    if (segment->purge_expire == 0) {\n      // no previous purgess, initialize now\n      segment->purge_expire = now + mi_option_get(mi_option_purge_delay);\n    }\n    else if (segment->purge_expire <= now) {\n      // previous purge mask already expired\n      if (segment->purge_expire + mi_option_get(mi_option_purge_extend_delay) <= now) {\n        mi_segment_try_purge(segment, true);\n      }\n      else {\n        segment->purge_expire = now + mi_option_get(mi_option_purge_extend_delay); // (mi_option_get(mi_option_purge_delay) / 8); // wait a tiny bit longer in case there is a series of free's\n      }\n    }\n    else {\n      // previous purge mask is not yet expired, increase the expiration by a bit.\n      segment->purge_expire += mi_option_get(mi_option_purge_extend_delay);\n    }\n  }\n}\n\nstatic void mi_segment_try_purge(mi_segment_t* segment, bool force) {\n  if (!segment->allow_purge || segment->purge_expire == 0 || mi_commit_mask_is_empty(&segment->purge_mask)) return;\n  mi_msecs_t now = _mi_clock_now();\n  if (!force && now < segment->purge_expire) return;\n\n  mi_commit_mask_t mask = segment->purge_mask;\n  segment->purge_expire = 0;\n  mi_commit_mask_create_empty(&segment->purge_mask);\n\n  size_t idx;\n  size_t count;\n  mi_commit_mask_foreach(&mask, idx, count) {\n    // if found, decommit that sequence\n    if (count > 0) {\n      uint8_t* p = (uint8_t*)segment + (idx*MI_COMMIT_SIZE);\n      size_t size = count * MI_COMMIT_SIZE;\n      mi_segment_purge(segment, p, size);\n    }\n  }\n  mi_commit_mask_foreach_end()\n  mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));\n}\n\n// called from `mi_heap_collect_ex`\n// this can be called per-page so it is important that try_purge has fast exit path\nvoid _mi_segment_collect(mi_segment_t* segment, bool force) {\n  mi_segment_try_purge(segment, force);\n}\n\n/* -----------------------------------------------------------\n   Span free\n----------------------------------------------------------- */\n\nstatic bool mi_segment_is_abandoned(mi_segment_t* segment) {\n  return (mi_atomic_load_relaxed(&segment->thread_id) == 0);\n}\n\n// note: can be called on abandoned segments\nstatic void mi_segment_span_free(mi_segment_t* segment, size_t slice_index, size_t slice_count, bool allow_purge, mi_segments_tld_t* tld) {\n  mi_assert_internal(slice_index < segment->slice_entries);\n  mi_span_queue_t* sq = (segment->kind == MI_SEGMENT_HUGE || mi_segment_is_abandoned(segment)\n                          ? NULL : mi_span_queue_for(slice_count,tld));\n  if (slice_count==0) slice_count = 1;\n  mi_assert_internal(slice_index + slice_count - 1 < segment->slice_entries);\n\n  // set first and last slice (the intermediates can be undetermined)\n  mi_slice_t* slice = &segment->slices[slice_index];\n  slice->slice_count = (uint32_t)slice_count;\n  mi_assert_internal(slice->slice_count == slice_count); // no overflow?\n  slice->slice_offset = 0;\n  if (slice_count > 1) {\n    mi_slice_t* last = slice + slice_count - 1;\n    mi_slice_t* end  = (mi_slice_t*)mi_segment_slices_end(segment);\n    if (last > end) { last = end; }\n    last->slice_count = 0;\n    last->slice_offset = (uint32_t)(sizeof(mi_page_t)*(slice_count - 1));\n    last->block_size = 0;\n  }\n\n  // perhaps decommit\n  if (allow_purge) {\n    mi_segment_schedule_purge(segment, mi_slice_start(slice), slice_count * MI_SEGMENT_SLICE_SIZE);\n  }\n\n  // and push it on the free page queue (if it was not a huge page)\n  if (sq != NULL) mi_span_queue_push( sq, slice );\n             else slice->block_size = 0; // mark huge page as free anyways\n}\n\n/*\n// called from reclaim to add existing free spans\nstatic void mi_segment_span_add_free(mi_slice_t* slice, mi_segments_tld_t* tld) {\n  mi_segment_t* segment = _mi_ptr_segment(slice);\n  mi_assert_internal(slice->xblock_size==0 && slice->slice_count>0 && slice->slice_offset==0);\n  size_t slice_index = mi_slice_index(slice);\n  mi_segment_span_free(segment,slice_index,slice->slice_count,tld);\n}\n*/\n\nstatic void mi_segment_span_remove_from_queue(mi_slice_t* slice, mi_segments_tld_t* tld) {\n  mi_assert_internal(slice->slice_count > 0 && slice->slice_offset==0 && slice->block_size==0);\n  mi_assert_internal(_mi_ptr_segment(slice)->kind != MI_SEGMENT_HUGE);\n  mi_span_queue_t* sq = mi_span_queue_for(slice->slice_count, tld);\n  mi_span_queue_delete(sq, slice);\n}\n\n// note: can be called on abandoned segments\nstatic mi_slice_t* mi_segment_span_free_coalesce(mi_slice_t* slice, mi_segments_tld_t* tld) {\n  mi_assert_internal(slice != NULL && slice->slice_count > 0 && slice->slice_offset == 0);\n  mi_segment_t* const segment = _mi_ptr_segment(slice);\n\n  // for huge pages, just mark as free but don't add to the queues\n  if (segment->kind == MI_SEGMENT_HUGE) {\n    // issue #691: segment->used can be 0 if the huge page block was freed while abandoned (reclaim will get here in that case)\n    mi_assert_internal((segment->used==0 && slice->block_size==0) || segment->used == 1);  // decreased right after this call in `mi_segment_page_clear`\n    slice->block_size = 0;  // mark as free anyways\n    // we should mark the last slice `xblock_size=0` now to maintain invariants but we skip it to\n    // avoid a possible cache miss (and the segment is about to be freed)\n    return slice;\n  }\n\n  // otherwise coalesce the span and add to the free span queues\n  const bool is_abandoned = (segment->thread_id == 0); // mi_segment_is_abandoned(segment);\n  size_t slice_count = slice->slice_count;\n  mi_slice_t* next = slice + slice->slice_count;\n  mi_assert_internal(next <= mi_segment_slices_end(segment));\n  if (next < mi_segment_slices_end(segment) && next->block_size==0) {\n    // free next block -- remove it from free and merge\n    mi_assert_internal(next->slice_count > 0 && next->slice_offset==0);\n    slice_count += next->slice_count; // extend\n    if (!is_abandoned) { mi_segment_span_remove_from_queue(next, tld); }\n  }\n  if (slice > segment->slices) {\n    mi_slice_t* prev = mi_slice_first(slice - 1);\n    mi_assert_internal(prev >= segment->slices);\n    if (prev->block_size==0) {\n      // free previous slice -- remove it from free and merge\n      mi_assert_internal(prev->slice_count > 0 && prev->slice_offset==0);\n      slice_count += prev->slice_count;\n      slice->slice_count = 0;\n      slice->slice_offset = (uint32_t)((uint8_t*)slice - (uint8_t*)prev); // set the slice offset for `segment_force_abandon` (in case the previous free block is very large).\n      if (!is_abandoned) { mi_segment_span_remove_from_queue(prev, tld); }\n      slice = prev;\n    }\n  }\n\n  // and add the new free page\n  mi_segment_span_free(segment, mi_slice_index(slice), slice_count, true, tld);\n  return slice;\n}\n\n\n\n/* -----------------------------------------------------------\n   Page allocation\n----------------------------------------------------------- */\n\n// Note: may still return NULL if committing the memory failed\nstatic mi_page_t* mi_segment_span_allocate(mi_segment_t* segment, size_t slice_index, size_t slice_count) {\n  mi_assert_internal(slice_index < segment->slice_entries);\n  mi_slice_t* const slice = &segment->slices[slice_index];\n  mi_assert_internal(slice->block_size==0 || slice->block_size==1);\n\n  // commit before changing the slice data\n  if (!mi_segment_ensure_committed(segment, _mi_segment_page_start_from_slice(segment, slice, 0, NULL), slice_count * MI_SEGMENT_SLICE_SIZE)) {\n    return NULL;  // commit failed!\n  }\n\n  // convert the slices to a page\n  slice->slice_offset = 0;\n  slice->slice_count = (uint32_t)slice_count;\n  mi_assert_internal(slice->slice_count == slice_count);\n  const size_t bsize = slice_count * MI_SEGMENT_SLICE_SIZE;\n  slice->block_size = bsize;\n  mi_page_t*  page = mi_slice_to_page(slice);\n  mi_assert_internal(mi_page_block_size(page) == bsize);\n\n  // set slice back pointers for the first MI_MAX_SLICE_OFFSET_COUNT entries\n  size_t extra = slice_count-1;\n  if (extra > MI_MAX_SLICE_OFFSET_COUNT) extra = MI_MAX_SLICE_OFFSET_COUNT;\n  if (slice_index + extra >= segment->slice_entries) extra = segment->slice_entries - slice_index - 1;  // huge objects may have more slices than avaiable entries in the segment->slices\n\n  mi_slice_t* slice_next = slice + 1;\n  for (size_t i = 1; i <= extra; i++, slice_next++) {\n    slice_next->slice_offset = (uint32_t)(sizeof(mi_slice_t)*i);\n    slice_next->slice_count = 0;\n    slice_next->block_size = 1;\n  }\n\n  // and also for the last one (if not set already) (the last one is needed for coalescing and for large alignments)\n  // note: the cast is needed for ubsan since the index can be larger than MI_SLICES_PER_SEGMENT for huge allocations (see #543)\n  mi_slice_t* last = slice + slice_count - 1;\n  mi_slice_t* end = (mi_slice_t*)mi_segment_slices_end(segment);\n  if (last > end) last = end;\n  if (last > slice) {\n    last->slice_offset = (uint32_t)(sizeof(mi_slice_t) * (last - slice));\n    last->slice_count = 0;\n    last->block_size = 1;\n  }\n\n  // and initialize the page\n  page->is_committed = true;\n  page->is_zero_init = segment->free_is_zero;\n  page->is_huge = (segment->kind == MI_SEGMENT_HUGE);\n  segment->used++;\n  return page;\n}\n\nstatic void mi_segment_slice_split(mi_segment_t* segment, mi_slice_t* slice, size_t slice_count, mi_segments_tld_t* tld) {\n  mi_assert_internal(_mi_ptr_segment(slice) == segment);\n  mi_assert_internal(slice->slice_count >= slice_count);\n  mi_assert_internal(slice->block_size > 0); // no more in free queue\n  if (slice->slice_count <= slice_count) return;\n  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);\n  size_t next_index = mi_slice_index(slice) + slice_count;\n  size_t next_count = slice->slice_count - slice_count;\n  mi_segment_span_free(segment, next_index, next_count, false /* don't purge left-over part */, tld);\n  slice->slice_count = (uint32_t)slice_count;\n}\n\nstatic mi_page_t* mi_segments_page_find_and_allocate(size_t slice_count, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld) {\n  mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_LARGE_OBJ_SIZE_MAX);\n  // search from best fit up\n  mi_span_queue_t* sq = mi_span_queue_for(slice_count, tld);\n  if (slice_count == 0) slice_count = 1;\n  while (sq <= &tld->spans[MI_SEGMENT_BIN_MAX]) {\n    for (mi_slice_t* slice = sq->first; slice != NULL; slice = slice->next) {\n      if (slice->slice_count >= slice_count) {\n        // found one\n        mi_segment_t* segment = _mi_ptr_segment(slice);\n        if (_mi_arena_memid_is_suitable(segment->memid, req_arena_id)) {\n          // found a suitable page span\n          mi_span_queue_delete(sq, slice);\n\n          if (slice->slice_count > slice_count) {\n            mi_segment_slice_split(segment, slice, slice_count, tld);\n          }\n          mi_assert_internal(slice != NULL && slice->slice_count == slice_count && slice->block_size > 0);\n          mi_page_t* page = mi_segment_span_allocate(segment, mi_slice_index(slice), slice->slice_count);\n          if (page == NULL) {\n            // commit failed; return NULL but first restore the slice\n            mi_segment_span_free_coalesce(slice, tld);\n            return NULL;\n          }\n          return page;\n        }\n      }\n    }\n    sq++;\n  }\n  // could not find a page..\n  return NULL;\n}\n\n\n/* -----------------------------------------------------------\n   Segment allocation\n----------------------------------------------------------- */\n\nstatic mi_segment_t* mi_segment_os_alloc( size_t required, size_t page_alignment, bool eager_delayed, mi_arena_id_t req_arena_id,\n                                          size_t* psegment_slices, size_t* pinfo_slices,\n                                          bool commit, mi_segments_tld_t* tld)\n\n{\n  mi_memid_t memid;\n  bool   allow_large = (!eager_delayed && (MI_SECURE == 0)); // only allow large OS pages once we are no longer lazy\n  size_t align_offset = 0;\n  size_t alignment = MI_SEGMENT_ALIGN;\n\n  if (page_alignment > 0) {\n    // mi_assert_internal(huge_page != NULL);\n    mi_assert_internal(page_alignment >= MI_SEGMENT_ALIGN);\n    alignment = page_alignment;\n    const size_t info_size = (*pinfo_slices) * MI_SEGMENT_SLICE_SIZE;\n    align_offset = _mi_align_up( info_size, MI_SEGMENT_ALIGN );\n    const size_t extra = align_offset - info_size;\n    // recalculate due to potential guard pages\n    *psegment_slices = mi_segment_calculate_slices(required + extra, pinfo_slices);\n    mi_assert_internal(*psegment_slices > 0 && *psegment_slices <= UINT32_MAX);\n  }\n\n  const size_t segment_size = (*psegment_slices) * MI_SEGMENT_SLICE_SIZE;\n  mi_segment_t* segment = (mi_segment_t*)_mi_arena_alloc_aligned(segment_size, alignment, align_offset, commit, allow_large, req_arena_id, &memid);\n  if (segment == NULL) {\n    return NULL;  // failed to allocate\n  }\n\n  // ensure metadata part of the segment is committed\n  mi_commit_mask_t commit_mask;\n  if (memid.initially_committed) {\n    mi_commit_mask_create_full(&commit_mask);\n  }\n  else {\n    // at least commit the info slices\n    const size_t commit_needed = _mi_divide_up((*pinfo_slices)*MI_SEGMENT_SLICE_SIZE, MI_COMMIT_SIZE);\n    mi_assert_internal(commit_needed>0);\n    mi_commit_mask_create(0, commit_needed, &commit_mask);\n    mi_assert_internal(commit_needed*MI_COMMIT_SIZE >= (*pinfo_slices)*MI_SEGMENT_SLICE_SIZE);\n    if (!_mi_os_commit(segment, commit_needed*MI_COMMIT_SIZE, NULL)) {\n      _mi_arena_free(segment,segment_size,0,memid);\n      return NULL;\n    }\n  }\n  mi_assert_internal(segment != NULL && (uintptr_t)segment % MI_SEGMENT_SIZE == 0);\n\n  segment->memid = memid;\n  segment->allow_decommit = !memid.is_pinned;\n  segment->allow_purge = segment->allow_decommit && (mi_option_get(mi_option_purge_delay) >= 0);\n  segment->segment_size = segment_size;\n  segment->subproc = tld->subproc;\n  segment->commit_mask = commit_mask;\n  segment->purge_expire = 0;\n  segment->free_is_zero = memid.initially_zero;\n  mi_commit_mask_create_empty(&segment->purge_mask);\n\n  mi_segments_track_size((long)(segment_size), tld);\n  _mi_segment_map_allocated_at(segment);\n  return segment;\n}\n\n\n// Allocate a segment from the OS aligned to `MI_SEGMENT_SIZE` .\nstatic mi_segment_t* mi_segment_alloc(size_t required, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld, mi_page_t** huge_page)\n{\n  mi_assert_internal((required==0 && huge_page==NULL) || (required>0 && huge_page != NULL));\n\n  // calculate needed sizes first\n  size_t info_slices;\n  size_t segment_slices = mi_segment_calculate_slices(required, &info_slices);\n  mi_assert_internal(segment_slices > 0 && segment_slices <= UINT32_MAX);\n\n  // Commit eagerly only if not the first N lazy segments (to reduce impact of many threads that allocate just a little)\n  const bool eager_delay = (// !_mi_os_has_overcommit() &&             // never delay on overcommit systems\n                            _mi_current_thread_count() > 1 &&       // do not delay for the first N threads\n                            tld->peak_count < (size_t)mi_option_get(mi_option_eager_commit_delay));\n  const bool eager = !eager_delay && mi_option_is_enabled(mi_option_eager_commit);\n  bool commit = eager || (required > 0);\n\n  // Allocate the segment from the OS\n  mi_segment_t* segment = mi_segment_os_alloc(required, page_alignment, eager_delay, req_arena_id,\n                                              &segment_slices, &info_slices, commit, tld);\n  if (segment == NULL) return NULL;\n\n  // zero the segment info? -- not always needed as it may be zero initialized from the OS\n  if (!segment->memid.initially_zero) {\n    ptrdiff_t ofs    = offsetof(mi_segment_t, next);\n    size_t    prefix = offsetof(mi_segment_t, slices) - ofs;\n    size_t    zsize  = prefix + (sizeof(mi_slice_t) * (segment_slices + 1)); // one more\n    _mi_memzero((uint8_t*)segment + ofs, zsize);\n  }\n\n  // initialize the rest of the segment info\n  const size_t slice_entries = (segment_slices > MI_SLICES_PER_SEGMENT ? MI_SLICES_PER_SEGMENT : segment_slices);\n  segment->segment_slices = segment_slices;\n  segment->segment_info_slices = info_slices;\n  segment->thread_id = _mi_thread_id();\n  segment->cookie = _mi_ptr_cookie(segment);\n  segment->slice_entries = slice_entries;\n  segment->kind = (required == 0 ? MI_SEGMENT_NORMAL : MI_SEGMENT_HUGE);\n\n  // _mi_memzero(segment->slices, sizeof(mi_slice_t)*(info_slices+1));\n  _mi_stat_increase(&tld->stats->page_committed, mi_segment_info_size(segment));\n\n  // set up guard pages\n  size_t guard_slices = 0;\n  if (MI_SECURE>0) {\n    // in secure mode, we set up a protected page in between the segment info\n    // and the page data, and at the end of the segment.\n    size_t os_pagesize = _mi_os_page_size();\n    _mi_os_protect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);\n    uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;\n    mi_segment_ensure_committed(segment, end, os_pagesize);\n    _mi_os_protect(end, os_pagesize);\n    if (slice_entries == segment_slices) segment->slice_entries--; // don't use the last slice :-(\n    guard_slices = 1;\n  }\n\n  // reserve first slices for segment info\n  mi_page_t* page0 = mi_segment_span_allocate(segment, 0, info_slices);\n  mi_assert_internal(page0!=NULL); if (page0==NULL) return NULL; // cannot fail as we always commit in advance\n  mi_assert_internal(segment->used == 1);\n  segment->used = 0; // don't count our internal slices towards usage\n\n  // initialize initial free pages\n  if (segment->kind == MI_SEGMENT_NORMAL) { // not a huge page\n    mi_assert_internal(huge_page==NULL);\n    mi_segment_span_free(segment, info_slices, segment->slice_entries - info_slices, false /* don't purge */, tld);\n  }\n  else {\n    mi_assert_internal(huge_page!=NULL);\n    mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));\n    mi_assert_internal(mi_commit_mask_is_full(&segment->commit_mask));\n    *huge_page = mi_segment_span_allocate(segment, info_slices, segment_slices - info_slices - guard_slices);\n    mi_assert_internal(*huge_page != NULL); // cannot fail as we commit in advance\n  }\n\n  mi_assert_expensive(mi_segment_is_valid(segment,tld));\n  return segment;\n}\n\n\nstatic void mi_segment_free(mi_segment_t* segment, bool force, mi_segments_tld_t* tld) {\n  MI_UNUSED(force);\n  mi_assert_internal(segment != NULL);\n  mi_assert_internal(segment->next == NULL);\n  mi_assert_internal(segment->used == 0);\n\n  // in `mi_segment_force_abandon` we set this to true to ensure the segment's memory stays valid\n  if (segment->dont_free) return;\n\n  // Remove the free pages\n  mi_slice_t* slice = &segment->slices[0];\n  const mi_slice_t* end = mi_segment_slices_end(segment);\n  #if MI_DEBUG>1\n  size_t page_count = 0;\n  #endif\n  while (slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    mi_assert_internal(mi_slice_index(slice)==0 || slice->block_size == 0); // no more used pages ..\n    if (slice->block_size == 0 && segment->kind != MI_SEGMENT_HUGE) {\n      mi_segment_span_remove_from_queue(slice, tld);\n    }\n    #if MI_DEBUG>1\n    page_count++;\n    #endif\n    slice = slice + slice->slice_count;\n  }\n  mi_assert_internal(page_count == 2); // first page is allocated by the segment itself\n\n  // stats\n  // _mi_stat_decrease(&tld->stats->page_committed, mi_segment_info_size(segment));\n\n  // return it to the OS\n  mi_segment_os_free(segment, tld);\n}\n\n\n/* -----------------------------------------------------------\n   Page Free\n----------------------------------------------------------- */\n\nstatic void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld);\n\n// note: can be called on abandoned pages\nstatic mi_slice_t* mi_segment_page_clear(mi_page_t* page, mi_segments_tld_t* tld) {\n  mi_assert_internal(page->block_size > 0);\n  mi_assert_internal(mi_page_all_free(page));\n  mi_segment_t* segment = _mi_ptr_segment(page);\n  mi_assert_internal(segment->used > 0);\n\n  size_t inuse = page->capacity * mi_page_block_size(page);\n  _mi_stat_decrease(&tld->stats->page_committed, inuse);\n  _mi_stat_decrease(&tld->stats->pages, 1);\n  _mi_stat_decrease(&tld->stats->page_bins[_mi_page_stats_bin(page)], 1);\n  \n  // reset the page memory to reduce memory pressure?\n  if (segment->allow_decommit && mi_option_is_enabled(mi_option_deprecated_page_reset)) {\n    size_t psize;\n    uint8_t* start = _mi_segment_page_start(segment, page, &psize);\n    _mi_os_reset(start, psize);\n  }\n\n  // zero the page data, but not the segment fields and heap tag\n  page->is_zero_init = false;\n  uint8_t heap_tag = page->heap_tag;\n  ptrdiff_t ofs = offsetof(mi_page_t, capacity);\n  _mi_memzero((uint8_t*)page + ofs, sizeof(*page) - ofs);\n  page->block_size = 1;\n  page->heap_tag = heap_tag;\n\n  // and free it\n  mi_slice_t* slice = mi_segment_span_free_coalesce(mi_page_to_slice(page), tld);\n  segment->used--;\n  segment->free_is_zero = false;\n\n  // cannot assert segment valid as it is called during reclaim\n  // mi_assert_expensive(mi_segment_is_valid(segment, tld));\n  return slice;\n}\n\nvoid _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld)\n{\n  mi_assert(page != NULL);\n  mi_segment_t* segment = _mi_page_segment(page);\n  mi_assert_expensive(mi_segment_is_valid(segment,tld));\n\n  // mark it as free now\n  mi_segment_page_clear(page, tld);\n  mi_assert_expensive(mi_segment_is_valid(segment, tld));\n\n  if (segment->used == 0) {\n    // no more used pages; remove from the free list and free the segment\n    mi_segment_free(segment, force, tld);\n  }\n  else if (segment->used == segment->abandoned) {\n    // only abandoned pages; remove from free list and abandon\n    mi_segment_abandon(segment,tld);\n  }\n  else {\n    // perform delayed purges\n    mi_segment_try_purge(segment, false /* force? */);\n  }\n}\n\n\n/* -----------------------------------------------------------\nAbandonment\n\nWhen threads terminate, they can leave segments with\nlive blocks (reachable through other threads). Such segments\nare \"abandoned\" and will be reclaimed by other threads to\nreuse their pages and/or free them eventually. The\n`thread_id` of such segments is 0.\n\nWhen a block is freed in an abandoned segment, the segment\nis reclaimed into that thread.\n\nMoreover, if threads are looking for a fresh segment, they\nwill first consider abandoned segments -- these can be found\nby scanning the arena memory\n(segments outside arena memoryare only reclaimed by a free).\n----------------------------------------------------------- */\n\n/* -----------------------------------------------------------\n   Abandon segment/page\n----------------------------------------------------------- */\n\nstatic void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {\n  mi_assert_internal(segment->used == segment->abandoned);\n  mi_assert_internal(segment->used > 0);\n  mi_assert_internal(segment->abandoned_visits == 0);\n  mi_assert_expensive(mi_segment_is_valid(segment,tld));\n\n  // remove the free pages from the free page queues\n  mi_slice_t* slice = &segment->slices[0];\n  const mi_slice_t* end = mi_segment_slices_end(segment);\n  while (slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    if (slice->block_size == 0) { // a free page\n      mi_segment_span_remove_from_queue(slice,tld);\n      slice->block_size = 0; // but keep it free\n    }\n    slice = slice + slice->slice_count;\n  }\n\n  // perform delayed decommits (forcing is much slower on mstress)\n  // Only abandoned segments in arena memory can be reclaimed without a free\n  // so if a segment is not from an arena we force purge here to be conservative.\n  const bool force_purge = (segment->memid.memkind != MI_MEM_ARENA) || mi_option_is_enabled(mi_option_abandoned_page_purge);\n  mi_segment_try_purge(segment, force_purge);\n\n  // all pages in the segment are abandoned; add it to the abandoned list\n  _mi_stat_increase(&tld->stats->segments_abandoned, 1);\n  mi_segments_track_size(-((long)mi_segment_size(segment)), tld);\n  segment->thread_id = 0;\n  segment->abandoned_visits = 1;   // from 0 to 1 to signify it is abandoned\n  if (segment->was_reclaimed) {\n    tld->reclaim_count--;\n    segment->was_reclaimed = false;\n  }\n  _mi_arena_segment_mark_abandoned(segment);\n}\n\nvoid _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) {\n  mi_assert(page != NULL);\n  mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);\n  mi_assert_internal(mi_page_heap(page) == NULL);\n  mi_segment_t* segment = _mi_page_segment(page);\n\n  mi_assert_expensive(mi_segment_is_valid(segment,tld));\n  segment->abandoned++;\n\n  _mi_stat_increase(&tld->stats->pages_abandoned, 1);\n  mi_assert_internal(segment->abandoned <= segment->used);\n  if (segment->used == segment->abandoned) {\n    // all pages are abandoned, abandon the entire segment\n    mi_segment_abandon(segment, tld);\n  }\n}\n\n/* -----------------------------------------------------------\n  Reclaim abandoned pages\n----------------------------------------------------------- */\n\nstatic mi_slice_t* mi_slices_start_iterate(mi_segment_t* segment, const mi_slice_t** end) {\n  mi_slice_t* slice = &segment->slices[0];\n  *end = mi_segment_slices_end(segment);\n  mi_assert_internal(slice->slice_count>0 && slice->block_size>0); // segment allocated page\n  slice = slice + slice->slice_count; // skip the first segment allocated page\n  return slice;\n}\n\n// Possibly free pages and check if free space is available\nstatic bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, size_t block_size, mi_segments_tld_t* tld)\n{\n  mi_assert_internal(mi_segment_is_abandoned(segment));\n  bool has_page = false;\n\n  // for all slices\n  const mi_slice_t* end;\n  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);\n  while (slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    if (mi_slice_is_used(slice)) { // used page\n      // ensure used count is up to date and collect potential concurrent frees\n      mi_page_t* const page = mi_slice_to_page(slice);\n      _mi_page_free_collect(page, false);\n      if (mi_page_all_free(page)) {\n        // if this page is all free now, free it without adding to any queues (yet)\n        mi_assert_internal(page->next == NULL && page->prev==NULL);\n        _mi_stat_decrease(&tld->stats->pages_abandoned, 1);\n        segment->abandoned--;\n        slice = mi_segment_page_clear(page, tld); // re-assign slice due to coalesce!\n        mi_assert_internal(!mi_slice_is_used(slice));\n        if (slice->slice_count >= slices_needed) {\n          has_page = true;\n        }\n      }\n      else if (mi_page_block_size(page) == block_size && mi_page_has_any_available(page)) {\n        // a page has available free blocks of the right size\n        has_page = true;\n      }\n    }\n    else {\n      // empty span\n      if (slice->slice_count >= slices_needed) {\n        has_page = true;\n      }\n    }\n    slice = slice + slice->slice_count;\n  }\n  return has_page;\n}\n\n// Reclaim an abandoned segment; returns NULL if the segment was freed\n// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.\nstatic mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) {\n  if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; }\n  // can be 0 still with abandoned_next, or already a thread id for segments outside an arena that are reclaimed on a free.\n  mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == 0 || mi_atomic_load_relaxed(&segment->thread_id) == _mi_thread_id());\n  mi_assert_internal(segment->subproc == heap->tld->segments.subproc); // only reclaim within the same subprocess\n  mi_atomic_store_release(&segment->thread_id, _mi_thread_id());\n  segment->abandoned_visits = 0;\n  segment->was_reclaimed = true;\n  tld->reclaim_count++;\n  mi_segments_track_size((long)mi_segment_size(segment), tld);\n  mi_assert_internal(segment->next == NULL);\n  _mi_stat_decrease(&tld->stats->segments_abandoned, 1);\n\n  // for all slices\n  const mi_slice_t* end;\n  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);\n  while (slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    if (mi_slice_is_used(slice)) {\n      // in use: reclaim the page in our heap\n      mi_page_t* page = mi_slice_to_page(slice);\n      mi_assert_internal(page->is_committed);\n      mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);\n      mi_assert_internal(mi_page_heap(page) == NULL);\n      mi_assert_internal(page->next == NULL && page->prev==NULL);\n      _mi_stat_decrease(&tld->stats->pages_abandoned, 1);\n      segment->abandoned--;\n      // get the target heap for this thread which has a matching heap tag (so we reclaim into a matching heap)\n      mi_heap_t* target_heap = _mi_heap_by_tag(heap, page->heap_tag);  // allow custom heaps to separate objects\n      if (target_heap == NULL) {\n        target_heap = heap;\n        _mi_error_message(EFAULT, \"page with tag %u cannot be reclaimed by a heap with the same tag (using heap tag %u instead)\\n\", page->heap_tag, heap->tag );\n      }\n      // associate the heap with this page, and allow heap thread delayed free again.\n      mi_page_set_heap(page, target_heap);\n      _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set)\n      _mi_page_free_collect(page, false); // ensure used count is up to date\n      if (mi_page_all_free(page)) {\n        // if everything free by now, free the page\n        slice = mi_segment_page_clear(page, tld);   // set slice again due to coalesceing\n      }\n      else {\n        // otherwise reclaim it into the heap\n        _mi_page_reclaim(target_heap, page);\n        if (requested_block_size == mi_page_block_size(page) && mi_page_has_any_available(page) && heap == target_heap) {\n          if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; }\n        }\n      }\n    }\n    else {\n      // the span is free, add it to our page queues\n      slice = mi_segment_span_free_coalesce(slice, tld); // set slice again due to coalesceing\n    }\n    mi_assert_internal(slice->slice_count>0 && slice->slice_offset==0);\n    slice = slice + slice->slice_count;\n  }\n\n  mi_assert(segment->abandoned == 0);\n  mi_assert_expensive(mi_segment_is_valid(segment, tld));\n  if (segment->used == 0) {  // due to page_clear\n    mi_assert_internal(right_page_reclaimed == NULL || !(*right_page_reclaimed));\n    mi_segment_free(segment, false, tld);\n    return NULL;\n  }\n  else {\n    return segment;\n  }\n}\n\n\n// attempt to reclaim a particular segment (called from multi threaded free `alloc.c:mi_free_block_mt`)\nbool _mi_segment_attempt_reclaim(mi_heap_t* heap, mi_segment_t* segment) {\n  if (mi_atomic_load_relaxed(&segment->thread_id) != 0) return false;  // it is not abandoned\n  if (segment->subproc != heap->tld->segments.subproc)  return false;  // only reclaim within the same subprocess\n  if (!_mi_heap_memid_is_suitable(heap,segment->memid)) return false;  // don't reclaim between exclusive and non-exclusive arena's\n  const long target = _mi_option_get_fast(mi_option_target_segments_per_thread);\n  if (target > 0 && (size_t)target <= heap->tld->segments.count) return false; // don't reclaim if going above the target count\n\n  // don't reclaim more from a `free` call than half the current segments\n  // this is to prevent a pure free-ing thread to start owning too many segments\n  // (but not for out-of-arena segments as that is the main way to be reclaimed for those)\n  if (segment->memid.memkind == MI_MEM_ARENA && heap->tld->segments.reclaim_count * 2 > heap->tld->segments.count) {\n    return false;\n  }\n  if (_mi_arena_segment_clear_abandoned(segment)) {  // atomically unabandon\n    mi_segment_t* res = mi_segment_reclaim(segment, heap, 0, NULL, &heap->tld->segments);\n    mi_assert_internal(res == segment);\n    return (res != NULL);\n  }\n  return false;\n}\n\nvoid _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) {\n  mi_segment_t* segment;\n  mi_arena_field_cursor_t current;\n  _mi_arena_field_cursor_init(heap, tld->subproc, true /* visit all, blocking */, &current);\n  while ((segment = _mi_arena_segment_clear_abandoned_next(&current)) != NULL) {\n    mi_segment_reclaim(segment, heap, 0, NULL, tld);\n  }\n  _mi_arena_field_cursor_done(&current);\n}\n\n\nstatic bool segment_count_is_within_target(mi_segments_tld_t* tld, size_t* ptarget) {\n  const size_t target = (size_t)mi_option_get_clamp(mi_option_target_segments_per_thread, 0, 1024);\n  if (ptarget != NULL) { *ptarget = target; }\n  return (target == 0 || tld->count < target);\n}\n\nstatic long mi_segment_get_reclaim_tries(mi_segments_tld_t* tld) {\n  // limit the tries to 10% (default) of the abandoned segments with at least 8 and at most 1024 tries.\n  const size_t perc = (size_t)mi_option_get_clamp(mi_option_max_segment_reclaim, 0, 100);\n  if (perc <= 0) return 0;\n  const size_t total_count = mi_atomic_load_relaxed(&tld->subproc->abandoned_count);\n  if (total_count == 0) return 0;\n  const size_t relative_count = (total_count > 10000 ? (total_count / 100) * perc : (total_count * perc) / 100); // avoid overflow\n  long max_tries = (long)(relative_count <= 1 ? 1 : (relative_count > 1024 ? 1024 : relative_count));\n  if (max_tries < 8 && total_count > 8) { max_tries = 8;  }\n  return max_tries;\n}\n\nstatic mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slices, size_t block_size, bool* reclaimed, mi_segments_tld_t* tld)\n{\n  *reclaimed = false;\n  long max_tries = mi_segment_get_reclaim_tries(tld);\n  if (max_tries <= 0) return NULL;\n\n  mi_segment_t* result = NULL;\n  mi_segment_t* segment = NULL;\n  mi_arena_field_cursor_t current;\n  _mi_arena_field_cursor_init(heap, tld->subproc, false /* non-blocking */, &current);\n  while (segment_count_is_within_target(tld,NULL) && (max_tries-- > 0) && ((segment = _mi_arena_segment_clear_abandoned_next(&current)) != NULL))\n  {\n    mi_assert(segment->subproc == heap->tld->segments.subproc); // cursor only visits segments in our sub-process\n    segment->abandoned_visits++;\n    // todo: should we respect numa affinity for abandoned reclaim? perhaps only for the first visit?\n    // todo: an arena exclusive heap will potentially visit many abandoned unsuitable segments and use many tries\n    // Perhaps we can skip non-suitable ones in a better way?\n    bool is_suitable = _mi_heap_memid_is_suitable(heap, segment->memid);\n    bool has_page = mi_segment_check_free(segment,needed_slices,block_size,tld); // try to free up pages (due to concurrent frees)\n    if (segment->used == 0) {\n      // free the segment (by forced reclaim) to make it available to other threads.\n      // note1: we prefer to free a segment as that might lead to reclaiming another\n      // segment that is still partially used.\n      // note2: we could in principle optimize this by skipping reclaim and directly\n      // freeing but that would violate some invariants temporarily)\n      mi_segment_reclaim(segment, heap, 0, NULL, tld);\n    }\n    else if (has_page && is_suitable) {\n      // found a large enough free span, or a page of the right block_size with free space\n      // we return the result of reclaim (which is usually `segment`) as it might free\n      // the segment due to concurrent frees (in which case `NULL` is returned).\n      result = mi_segment_reclaim(segment, heap, block_size, reclaimed, tld);\n      break;\n    }\n    else if (segment->abandoned_visits > 3 && is_suitable) {\n      // always reclaim on 3rd visit to limit the abandoned segment count.\n      mi_segment_reclaim(segment, heap, 0, NULL, tld);\n    }\n    else {\n      // otherwise, push on the visited list so it gets not looked at too quickly again\n      max_tries++; // don't count this as a try since it was not suitable\n      mi_segment_try_purge(segment, false /* true force? */); // force purge if needed as we may not visit soon again\n      _mi_arena_segment_mark_abandoned(segment);\n    }\n  }\n  _mi_arena_field_cursor_done(&current);\n  return result;\n}\n\n// collect abandoned segments\nvoid _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)\n{\n  mi_segment_t* segment;\n  mi_arena_field_cursor_t current; _mi_arena_field_cursor_init(heap, tld->subproc, force /* blocking? */, &current);\n  long max_tries = (force ? (long)mi_atomic_load_relaxed(&tld->subproc->abandoned_count) : 1024);  // limit latency\n  while ((max_tries-- > 0) && ((segment = _mi_arena_segment_clear_abandoned_next(&current)) != NULL)) {\n    mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees)\n    if (segment->used == 0) {\n      // free the segment (by forced reclaim) to make it available to other threads.\n      // note: we could in principle optimize this by skipping reclaim and directly\n      // freeing but that would violate some invariants temporarily)\n      mi_segment_reclaim(segment, heap, 0, NULL, tld);\n    }\n    else {\n      // otherwise, purge if needed and push on the visited list\n      // note: forced purge can be expensive if many threads are destroyed/created as in mstress.\n      mi_segment_try_purge(segment, force);\n      _mi_arena_segment_mark_abandoned(segment);\n    }\n  }\n  _mi_arena_field_cursor_done(&current);\n}\n\n/* -----------------------------------------------------------\n   Force abandon a segment that is in use by our thread\n----------------------------------------------------------- */\n\n// force abandon a segment\nstatic void mi_segment_force_abandon(mi_segment_t* segment, mi_segments_tld_t* tld)\n{\n  mi_assert_internal(!mi_segment_is_abandoned(segment));\n  mi_assert_internal(!segment->dont_free);\n\n  // ensure the segment does not get free'd underneath us (so we can check if a page has been freed in `mi_page_force_abandon`)\n  segment->dont_free = true;\n\n  // for all slices\n  const mi_slice_t* end;\n  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);\n  while (slice < end) {\n    mi_assert_internal(slice->slice_count > 0);\n    mi_assert_internal(slice->slice_offset == 0);\n    if (mi_slice_is_used(slice)) {\n      // ensure used count is up to date and collect potential concurrent frees\n      mi_page_t* const page = mi_slice_to_page(slice);\n      _mi_page_free_collect(page, false);\n      {\n        // abandon the page if it is still in-use (this will free it if possible as well)\n        mi_assert_internal(segment->used > 0);\n        if (segment->used == segment->abandoned+1) {\n          // the last page.. abandon and return as the segment will be abandoned after this\n          // and we should no longer access it.\n          segment->dont_free = false;\n          _mi_page_force_abandon(page);\n          return;\n        }\n        else {\n          // abandon and continue\n          _mi_page_force_abandon(page);\n          // it might be freed, reset the slice (note: relies on coalesce setting the slice_offset)\n          slice = mi_slice_first(slice);\n        }\n      }\n    }\n    slice = slice + slice->slice_count;\n  }\n  segment->dont_free = false;\n  mi_assert(segment->used == segment->abandoned);\n  mi_assert(segment->used == 0);\n  if (segment->used == 0) {  // paranoia\n    // all free now\n    mi_segment_free(segment, false, tld);\n  }\n  else {\n    // perform delayed purges\n    mi_segment_try_purge(segment, false /* force? */);\n  }\n}\n\n\n// try abandon segments.\n// this should be called from `reclaim_or_alloc` so we know all segments are (about) fully in use.\nstatic void mi_segments_try_abandon_to_target(mi_heap_t* heap, size_t target, mi_segments_tld_t* tld) {\n  if (target <= 1) return;\n  const size_t min_target = (target > 4 ? (target*3)/4 : target);  // 75%\n  // todo: we should maintain a list of segments per thread; for now, only consider segments from the heap full pages\n  for (int i = 0; i < 64 && tld->count >= min_target; i++) {\n    mi_page_t* page = heap->pages[MI_BIN_FULL].first;\n    while (page != NULL && mi_page_block_size(page) > MI_LARGE_OBJ_SIZE_MAX) {\n      page = page->next;\n    }\n    if (page==NULL) {\n      break;\n    }\n    mi_segment_t* segment = _mi_page_segment(page);\n    mi_segment_force_abandon(segment, tld);\n    mi_assert_internal(page != heap->pages[MI_BIN_FULL].first); // as it is just abandoned\n  }\n}\n\n// try abandon segments.\n// this should be called from `reclaim_or_alloc` so we know all segments are (about) fully in use.\nstatic void mi_segments_try_abandon(mi_heap_t* heap, mi_segments_tld_t* tld) {\n  // we call this when we are about to add a fresh segment so we should be under our target segment count.\n  size_t target = 0;\n  if (segment_count_is_within_target(tld, &target)) return;\n  mi_segments_try_abandon_to_target(heap, target, tld);\n}\n\nvoid mi_collect_reduce(size_t target_size) mi_attr_noexcept {\n  mi_collect(true);\n  mi_heap_t* heap = mi_heap_get_default();\n  mi_segments_tld_t* tld = &heap->tld->segments;\n  size_t target = target_size / MI_SEGMENT_SIZE;\n  if (target == 0) {\n    target = (size_t)mi_option_get_clamp(mi_option_target_segments_per_thread, 1, 1024);\n  }\n  mi_segments_try_abandon_to_target(heap, target, tld);\n}\n\n/* -----------------------------------------------------------\n   Reclaim or allocate\n----------------------------------------------------------- */\n\nstatic mi_segment_t* mi_segment_reclaim_or_alloc(mi_heap_t* heap, size_t needed_slices, size_t block_size, mi_segments_tld_t* tld)\n{\n  mi_assert_internal(block_size <= MI_LARGE_OBJ_SIZE_MAX);\n\n  // try to abandon some segments to increase reuse between threads\n  mi_segments_try_abandon(heap,tld);\n\n  // 1. try to reclaim an abandoned segment\n  bool reclaimed;\n  mi_segment_t* segment = mi_segment_try_reclaim(heap, needed_slices, block_size, &reclaimed, tld);\n  if (reclaimed) {\n    // reclaimed the right page right into the heap\n    mi_assert_internal(segment != NULL);\n    return NULL; // pretend out-of-memory as the page will be in the page queue of the heap with available blocks\n  }\n  else if (segment != NULL) {\n    // reclaimed a segment with a large enough empty span in it\n    return segment;\n  }\n  // 2. otherwise allocate a fresh segment\n  return mi_segment_alloc(0, 0, heap->arena_id, tld, NULL);\n}\n\n\n/* -----------------------------------------------------------\n   Page allocation\n----------------------------------------------------------- */\n\nstatic mi_page_t* mi_segments_page_alloc(mi_heap_t* heap, mi_page_kind_t page_kind, size_t required, size_t block_size, mi_segments_tld_t* tld)\n{\n  mi_assert_internal(required <= MI_LARGE_OBJ_SIZE_MAX && page_kind <= MI_PAGE_LARGE);\n\n  // find a free page\n  size_t page_size = _mi_align_up(required, (required > MI_MEDIUM_PAGE_SIZE ? MI_MEDIUM_PAGE_SIZE : MI_SEGMENT_SLICE_SIZE));\n  size_t slices_needed = page_size / MI_SEGMENT_SLICE_SIZE;\n  mi_assert_internal(slices_needed * MI_SEGMENT_SLICE_SIZE == page_size);\n  mi_page_t* page = mi_segments_page_find_and_allocate(slices_needed, heap->arena_id, tld); //(required <= MI_SMALL_SIZE_MAX ? 0 : slices_needed), tld);\n  if (page==NULL) {\n    // no free page, allocate a new segment and try again\n    if (mi_segment_reclaim_or_alloc(heap, slices_needed, block_size, tld) == NULL) {\n      // OOM or reclaimed a good page in the heap\n      return NULL;\n    }\n    else {\n      // otherwise try again\n      return mi_segments_page_alloc(heap, page_kind, required, block_size, tld);\n    }\n  }\n  mi_assert_internal(page != NULL && page->slice_count*MI_SEGMENT_SLICE_SIZE == page_size);\n  mi_assert_internal(_mi_ptr_segment(page)->thread_id == _mi_thread_id());\n  mi_segment_try_purge(_mi_ptr_segment(page), false);\n  return page;\n}\n\n\n\n/* -----------------------------------------------------------\n   Huge page allocation\n----------------------------------------------------------- */\n\nstatic mi_page_t* mi_segment_huge_page_alloc(size_t size, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld)\n{\n  mi_page_t* page = NULL;\n  mi_segment_t* segment = mi_segment_alloc(size,page_alignment,req_arena_id,tld,&page);\n  if (segment == NULL || page==NULL) return NULL;\n  mi_assert_internal(segment->used==1);\n  mi_assert_internal(mi_page_block_size(page) >= size);\n  #if MI_HUGE_PAGE_ABANDON\n  segment->thread_id = 0; // huge segments are immediately abandoned\n  #endif\n\n  // for huge pages we initialize the block_size as we may\n  // overallocate to accommodate large alignments.\n  size_t psize;\n  uint8_t* start = _mi_segment_page_start(segment, page, &psize);\n  page->block_size = psize;\n  mi_assert_internal(page->is_huge);\n\n  // decommit the part of the prefix of a page that will not be used; this can be quite large (close to MI_SEGMENT_SIZE)\n  if (page_alignment > 0 && segment->allow_decommit) {\n    uint8_t* aligned_p = (uint8_t*)_mi_align_up((uintptr_t)start, page_alignment);\n    mi_assert_internal(_mi_is_aligned(aligned_p, page_alignment));\n    mi_assert_internal(psize - (aligned_p - start) >= size);\n    uint8_t* decommit_start = start + sizeof(mi_block_t);              // for the free list\n    ptrdiff_t decommit_size = aligned_p - decommit_start;\n    _mi_os_reset(decommit_start, decommit_size);   // note: cannot use segment_decommit on huge segments\n  }\n\n  return page;\n}\n\n#if MI_HUGE_PAGE_ABANDON\n// free huge block from another thread\nvoid _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) {\n  // huge page segments are always abandoned and can be freed immediately by any thread\n  mi_assert_internal(segment->kind==MI_SEGMENT_HUGE);\n  mi_assert_internal(segment == _mi_page_segment(page));\n  mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id)==0);\n\n  // claim it and free\n  mi_heap_t* heap = mi_heap_get_default(); // issue #221; don't use the internal get_default_heap as we need to ensure the thread is initialized.\n  // paranoia: if this it the last reference, the cas should always succeed\n  size_t expected_tid = 0;\n  if (mi_atomic_cas_strong_acq_rel(&segment->thread_id, &expected_tid, heap->thread_id)) {\n    mi_block_set_next(page, block, page->free);\n    page->free = block;\n    page->used--;\n    page->is_zero_init = false;\n    mi_assert(page->used == 0);\n    mi_tld_t* tld = heap->tld;\n    _mi_segment_page_free(page, true, &tld->segments);\n  }\n#if (MI_DEBUG!=0)\n  else {\n    mi_assert_internal(false);\n  }\n#endif\n}\n\n#else\n// reset memory of a huge block from another thread\nvoid _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) {\n  MI_UNUSED(page);\n  mi_assert_internal(segment->kind == MI_SEGMENT_HUGE);\n  mi_assert_internal(segment == _mi_page_segment(page));\n  mi_assert_internal(page->used == 1); // this is called just before the free\n  mi_assert_internal(page->free == NULL);\n  if (segment->allow_decommit) {\n    size_t csize = mi_usable_size(block);\n    if (csize > sizeof(mi_block_t)) {\n      csize = csize - sizeof(mi_block_t);\n      uint8_t* p = (uint8_t*)block + sizeof(mi_block_t);\n      _mi_os_reset(p, csize);  // note: cannot use segment_decommit on huge segments\n    }\n  }\n}\n#endif\n\n/* -----------------------------------------------------------\n   Page allocation and free\n----------------------------------------------------------- */\nmi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment, mi_segments_tld_t* tld) {\n  mi_page_t* page;\n  if mi_unlikely(page_alignment > MI_BLOCK_ALIGNMENT_MAX) {\n    mi_assert_internal(_mi_is_power_of_two(page_alignment));\n    mi_assert_internal(page_alignment >= MI_SEGMENT_SIZE);\n    if (page_alignment < MI_SEGMENT_SIZE) { page_alignment = MI_SEGMENT_SIZE; }\n    page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld);\n  }\n  else if (block_size <= MI_SMALL_OBJ_SIZE_MAX) {\n    page = mi_segments_page_alloc(heap,MI_PAGE_SMALL,block_size,block_size,tld);\n  }\n  else if (block_size <= MI_MEDIUM_OBJ_SIZE_MAX) {\n    page = mi_segments_page_alloc(heap,MI_PAGE_MEDIUM,MI_MEDIUM_PAGE_SIZE,block_size,tld);\n  }\n  else if (block_size <= MI_LARGE_OBJ_SIZE_MAX) {\n    page = mi_segments_page_alloc(heap,MI_PAGE_LARGE,block_size,block_size,tld);\n  }\n  else {\n    page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld);\n  }\n  mi_assert_internal(page == NULL || _mi_heap_memid_is_suitable(heap, _mi_page_segment(page)->memid));\n  mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld));\n  mi_assert_internal(page == NULL || _mi_page_segment(page)->subproc == tld->subproc);\n  return page;\n}\n\n\n/* -----------------------------------------------------------\n   Visit blocks in a segment (only used for abandoned segments)\n----------------------------------------------------------- */\n\nstatic bool mi_segment_visit_page(mi_page_t* page, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {\n  mi_heap_area_t area;\n  _mi_heap_area_init(&area, page);\n  if (!visitor(NULL, &area, NULL, area.block_size, arg)) return false;\n  if (visit_blocks) {\n    return _mi_heap_area_visit_blocks(&area, page, visitor, arg);\n  }\n  else {\n    return true;\n  }\n}\n\nbool _mi_segment_visit_blocks(mi_segment_t* segment, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {\n  const mi_slice_t* end;\n  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);\n  while (slice < end) {\n    if (mi_slice_is_used(slice)) {\n      mi_page_t* const page = mi_slice_to_page(slice);\n      if (heap_tag < 0 || (int)page->heap_tag == heap_tag) {\n        if (!mi_segment_visit_page(page, visit_blocks, visitor, arg)) return false;\n      }\n    }\n    slice = slice + slice->slice_count;\n  }\n  return true;\n}\n"
  },
  {
    "path": "src/static.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#ifndef _DEFAULT_SOURCE\n#define _DEFAULT_SOURCE\n#endif\n#if defined(__sun)\n// same remarks as os.c for the static's context.\n#undef _XOPEN_SOURCE\n#undef _POSIX_C_SOURCE\n#endif\n\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n\n// For a static override we create a single object file\n// containing the whole library. If it is linked first\n// it will override all the standard library allocation\n// functions (on Unix's).\n#include \"alloc.c\"          // includes alloc-override.c\n#include \"alloc-aligned.c\"\n#include \"alloc-posix.c\"\n#include \"arena.c\"\n#include \"bitmap.c\"\n#include \"heap.c\"\n#include \"init.c\"\n#include \"libc.c\"\n#include \"options.c\"\n#include \"os.c\"\n#include \"page.c\"           // includes page-queue.c\n#include \"random.c\"\n#include \"segment.c\"\n#include \"segment-map.c\"\n#include \"stats.c\"\n#include \"prim/prim.c\"\n#if MI_OSX_ZONE\n#include \"prim/osx/alloc-override-zone.c\"\n#endif\n"
  },
  {
    "path": "src/stats.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2021, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/internal.h\"\n#include \"mimalloc/atomic.h\"\n#include \"mimalloc/prim.h\"\n\n#include <string.h> // memset\n\n#if defined(_MSC_VER) && (_MSC_VER < 1920)\n#pragma warning(disable:4204)  // non-constant aggregate initializer\n#endif\n\n/* -----------------------------------------------------------\n  Statistics operations\n----------------------------------------------------------- */\n\nstatic bool mi_is_in_main(void* stat) {\n  return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main\n         && (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t)));\n}\n\nstatic void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {\n  if (amount == 0) return;\n  if mi_unlikely(mi_is_in_main(stat))\n  {\n    // add atomically (for abandoned pages)\n    int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);\n    // if (stat == &_mi_stats_main.committed) { mi_assert_internal(current + amount >= 0); };\n    mi_atomic_maxi64_relaxed(&stat->peak, current + amount);\n    if (amount > 0) {\n      mi_atomic_addi64_relaxed(&stat->total,amount);\n    }\n  }\n  else {\n    // add thread local\n    stat->current += amount;\n    if (stat->current > stat->peak) { stat->peak = stat->current; }\n    if (amount > 0) { stat->total += amount; }\n  }\n}\n\nvoid _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {\n  if (mi_is_in_main(stat)) {\n    mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );\n  }\n  else {\n    stat->total += amount;\n  }\n}\n\nvoid _mi_stat_increase(mi_stat_count_t* stat, size_t amount) {\n  mi_stat_update(stat, (int64_t)amount);\n}\n\nvoid _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {\n  mi_stat_update(stat, -((int64_t)amount));\n}\n\n\nstatic void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount) {\n  if (amount == 0) return;\n  if mi_unlikely(mi_is_in_main(stat))\n  {\n    // adjust atomically \n    mi_atomic_addi64_relaxed(&stat->current, amount);\n    mi_atomic_addi64_relaxed(&stat->total,amount);\n  }\n  else {\n    // adjust local\n    stat->current += amount;\n    stat->total += amount;\n  }\n}\n\nvoid _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount) {\n  mi_stat_adjust(stat, -((int64_t)amount));\n}\n\n\n// must be thread safe as it is called from stats_merge\nstatic void mi_stat_count_add_mt(mi_stat_count_t* stat, const mi_stat_count_t* src) {\n  if (stat==src) return;\n  mi_atomic_void_addi64_relaxed(&stat->total, &src->total); \n  const int64_t prev_current = mi_atomic_addi64_relaxed(&stat->current, src->current);\n\n  // Global current plus thread peak approximates new global peak\n  // note: peak scores do really not work across threads.\n  // we used to just add them together but that often overestimates in practice.\n  // similarly, max does not seem to work well. The current approach\n  // by Artem Kharytoniuk (@artem-lunarg) seems to work better, see PR#1112 \n  // for a longer description.\n  mi_atomic_maxi64_relaxed(&stat->peak, prev_current + src->peak);\n}\n\nstatic void mi_stat_counter_add_mt(mi_stat_counter_t* stat, const mi_stat_counter_t* src) {\n  if (stat==src) return;\n  mi_atomic_void_addi64_relaxed(&stat->total, &src->total);\n}\n\n#define MI_STAT_COUNT(stat)    mi_stat_count_add_mt(&stats->stat, &src->stat);\n#define MI_STAT_COUNTER(stat)  mi_stat_counter_add_mt(&stats->stat, &src->stat);\n\n// must be thread safe as it is called from stats_merge\nstatic void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {\n  if (stats==src) return;\n\n  // copy all fields\n  MI_STAT_FIELDS()\n\n  #if MI_STAT>1\n  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {\n    mi_stat_count_add_mt(&stats->malloc_bins[i], &src->malloc_bins[i]);\n  }\n  #endif\n  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {\n    mi_stat_count_add_mt(&stats->page_bins[i], &src->page_bins[i]);\n  }\n}\n\n#undef MI_STAT_COUNT\n#undef MI_STAT_COUNTER\n\n/* -----------------------------------------------------------\n  Display statistics\n----------------------------------------------------------- */\n\n// unit > 0 : size in binary bytes\n// unit == 0: count as decimal\n// unit < 0 : count in binary\nstatic void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) {\n  char buf[32]; buf[0] = 0;\n  int  len = 32;\n  const char* suffix = (unit <= 0 ? \" \" : \"B\");\n  const int64_t base = (unit == 0 ? 1000 : 1024);\n  if (unit>0) n *= unit;\n\n  const int64_t pos = (n < 0 ? -n : n);\n  if (pos < base) {\n    if (n!=1 || suffix[0] != 'B') {  // skip printing 1 B for the unit column\n      _mi_snprintf(buf, len, \"%lld   %-3s\", (long long)n, (n==0 ? \"\" : suffix));\n    }\n  }\n  else {\n    int64_t divider = base;\n    const char* magnitude = \"K\";\n    if (pos >= divider*base) { divider *= base; magnitude = \"M\"; }\n    if (pos >= divider*base) { divider *= base; magnitude = \"G\"; }\n    const int64_t tens = (n / (divider/10));\n    const long whole = (long)(tens/10);\n    const long frac1 = (long)(tens%10);\n    char unitdesc[8];\n    _mi_snprintf(unitdesc, 8, \"%s%s%s\", magnitude, (base==1024 ? \"i\" : \"\"), suffix);\n    _mi_snprintf(buf, len, \"%ld.%ld %-3s\", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);\n  }\n  _mi_fprintf(out, arg, (fmt==NULL ? \"%12s\" : fmt), buf);\n}\n\n\nstatic void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {\n  mi_printf_amount(n,unit,out,arg,NULL);\n}\n\nstatic void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {\n  if (unit==1) _mi_fprintf(out, arg, \"%12s\",\" \");\n          else mi_print_amount(n,0,out,arg);\n}\n\nstatic void mi_stat_print_ex(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg, const char* notok ) {\n  _mi_fprintf(out, arg,\"%10s:\", msg);\n  if (unit != 0) {\n    if (unit > 0) {\n      mi_print_amount(stat->peak, unit, out, arg);\n      mi_print_amount(stat->total, unit, out, arg);\n      // mi_print_amount(stat->freed, unit, out, arg);\n      mi_print_amount(stat->current, unit, out, arg);\n      mi_print_amount(unit, 1, out, arg);\n      mi_print_count(stat->total, unit, out, arg);\n    }\n    else {\n      mi_print_amount(stat->peak, -1, out, arg);\n      mi_print_amount(stat->total, -1, out, arg);\n      // mi_print_amount(stat->freed, -1, out, arg);\n      mi_print_amount(stat->current, -1, out, arg);\n      if (unit == -1) {\n        _mi_fprintf(out, arg, \"%24s\", \"\");\n      }\n      else {\n        mi_print_amount(-unit, 1, out, arg);\n        mi_print_count((stat->total / -unit), 0, out, arg);\n      }\n    }\n    if (stat->current != 0) {\n      _mi_fprintf(out, arg, \"  \");\n      _mi_fprintf(out, arg, (notok == NULL ? \"not all freed\" : notok));\n      _mi_fprintf(out, arg, \"\\n\");\n    }\n    else {\n      _mi_fprintf(out, arg, \"  ok\\n\");\n    }\n  }\n  else {\n    mi_print_amount(stat->peak, 1, out, arg);\n    mi_print_amount(stat->total, 1, out, arg);\n    _mi_fprintf(out, arg, \"%11s\", \" \");  // no freed\n    mi_print_amount(stat->current, 1, out, arg);\n    _mi_fprintf(out, arg, \"\\n\");\n  }\n}\n\nstatic void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {\n  mi_stat_print_ex(stat, msg, unit, out, arg, NULL);\n}\n\n#if MI_STAT>1\nstatic void mi_stat_total_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {\n  _mi_fprintf(out, arg, \"%10s:\", msg);\n  _mi_fprintf(out, arg, \"%12s\", \" \");  // no peak\n  mi_print_amount(stat->total, unit, out, arg);\n  _mi_fprintf(out, arg, \"\\n\");\n}\n#endif\n\nstatic void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {\n  _mi_fprintf(out, arg, \"%10s:\", msg);\n  mi_print_amount(stat->total, -1, out, arg);\n  _mi_fprintf(out, arg, \"\\n\");\n}\n\n\nstatic void mi_stat_average_print(size_t count, size_t total, const char* msg, mi_output_fun* out, void* arg) {\n  const int64_t avg_tens = (count == 0 ? 0 : (total*10 / count));\n  const long avg_whole = (long)(avg_tens/10);\n  const long avg_frac1 = (long)(avg_tens%10);\n  _mi_fprintf(out, arg, \"%10s: %5ld.%ld avg\\n\", msg, avg_whole, avg_frac1);\n}\n\n\nstatic void mi_print_header(mi_output_fun* out, void* arg ) {\n  _mi_fprintf(out, arg, \"%10s: %11s %11s %11s %11s %11s\\n\", \"heap stats\", \"peak   \", \"total   \", \"current   \", \"block   \", \"total#   \");\n}\n\n#if MI_STAT>1\nstatic void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) {\n  bool found = false;\n  char buf[64];\n  for (size_t i = 0; i <= max; i++) {\n    if (bins[i].total > 0) {\n      found = true;\n      int64_t unit = _mi_bin_size((uint8_t)i);\n      _mi_snprintf(buf, 64, \"%s %3lu\", fmt, (long)i);\n      mi_stat_print(&bins[i], buf, unit, out, arg);\n    }\n  }\n  if (found) {\n    _mi_fprintf(out, arg, \"\\n\");\n    mi_print_header(out, arg);\n  }\n}\n#endif\n\n\n\n//------------------------------------------------------------\n// Use an output wrapper for line-buffered output\n// (which is nice when using loggers etc.)\n//------------------------------------------------------------\ntypedef struct buffered_s {\n  mi_output_fun* out;   // original output function\n  void*          arg;   // and state\n  char*          buf;   // local buffer of at least size `count+1`\n  size_t         used;  // currently used chars `used <= count`\n  size_t         count; // total chars available for output\n} buffered_t;\n\nstatic void mi_buffered_flush(buffered_t* buf) {\n  buf->buf[buf->used] = 0;\n  _mi_fputs(buf->out, buf->arg, NULL, buf->buf);\n  buf->used = 0;\n}\n\nstatic void mi_cdecl mi_buffered_out(const char* msg, void* arg) {\n  buffered_t* buf = (buffered_t*)arg;\n  if (msg==NULL || buf==NULL) return;\n  for (const char* src = msg; *src != 0; src++) {\n    char c = *src;\n    if (buf->used >= buf->count) mi_buffered_flush(buf);\n    mi_assert_internal(buf->used < buf->count);\n    buf->buf[buf->used++] = c;\n    if (c == '\\n') mi_buffered_flush(buf);\n  }\n}\n\n//------------------------------------------------------------\n// Print statistics\n//------------------------------------------------------------\n\nstatic void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept {\n  // wrap the output function to be line buffered\n  char buf[256];\n  buffered_t buffer = { out0, arg0, NULL, 0, 255 };\n  buffer.buf = buf;\n  mi_output_fun* out = &mi_buffered_out;\n  void* arg = &buffer;\n\n  // and print using that\n  mi_print_header(out,arg);\n  #if MI_STAT>1\n  mi_stats_print_bins(stats->malloc_bins, MI_BIN_HUGE, \"bin\",out,arg);\n  #endif\n  #if MI_STAT\n  mi_stat_print(&stats->malloc_normal, \"binned\", (stats->malloc_normal_count.total == 0 ? 1 : -1), out, arg);\n  // mi_stat_print(&stats->malloc_large, \"large\", (stats->malloc_large_count.total == 0 ? 1 : -1), out, arg);\n  mi_stat_print(&stats->malloc_huge, \"huge\", (stats->malloc_huge_count.total == 0 ? 1 : -1), out, arg);\n  mi_stat_count_t total = { 0,0,0 };\n  mi_stat_count_add_mt(&total, &stats->malloc_normal);\n  // mi_stat_count_add(&total, &stats->malloc_large);\n  mi_stat_count_add_mt(&total, &stats->malloc_huge);\n  mi_stat_print_ex(&total, \"total\", 1, out, arg, \"\");\n  #endif\n  #if MI_STAT>1\n  mi_stat_total_print(&stats->malloc_requested, \"malloc req\", 1, out, arg);\n  _mi_fprintf(out, arg, \"\\n\");\n  #endif\n  mi_stat_print_ex(&stats->reserved, \"reserved\", 1, out, arg, \"\");\n  mi_stat_print_ex(&stats->committed, \"committed\", 1, out, arg, \"\");\n  mi_stat_counter_print(&stats->reset, \"reset\", out, arg );\n  mi_stat_counter_print(&stats->purged, \"purged\", out, arg );\n  mi_stat_print_ex(&stats->page_committed, \"touched\", 1, out, arg, \"\");\n  mi_stat_print(&stats->segments, \"segments\", -1, out, arg);\n  mi_stat_print(&stats->segments_abandoned, \"-abandoned\", -1, out, arg);\n  mi_stat_print(&stats->segments_cache, \"-cached\", -1, out, arg);\n  mi_stat_print(&stats->pages, \"pages\", -1, out, arg);\n  mi_stat_print(&stats->pages_abandoned, \"-abandoned\", -1, out, arg);\n  mi_stat_counter_print(&stats->pages_extended, \"-extended\", out, arg);\n  mi_stat_counter_print(&stats->pages_retire, \"-retire\", out, arg);\n  mi_stat_counter_print(&stats->arena_count, \"arenas\", out, arg);\n  // mi_stat_counter_print(&stats->arena_crossover_count, \"-crossover\", out, arg);\n  mi_stat_counter_print(&stats->arena_rollback_count, \"-rollback\", out, arg);\n  mi_stat_counter_print(&stats->mmap_calls, \"mmaps\", out, arg);\n  mi_stat_counter_print(&stats->commit_calls, \"commits\", out, arg);\n  mi_stat_counter_print(&stats->reset_calls, \"resets\", out, arg);\n  mi_stat_counter_print(&stats->purge_calls, \"purges\", out, arg);\n  mi_stat_counter_print(&stats->malloc_guarded_count, \"guarded\", out, arg);\n  mi_stat_print(&stats->threads, \"threads\", -1, out, arg);\n  mi_stat_average_print(stats->page_searches_count.total, stats->page_searches.total, \"searches\", out, arg);\n  _mi_fprintf(out, arg, \"%10s: %5i\\n\", \"numa nodes\", _mi_os_numa_node_count());\n\n  size_t elapsed;\n  size_t user_time;\n  size_t sys_time;\n  size_t current_rss;\n  size_t peak_rss;\n  size_t current_commit;\n  size_t peak_commit;\n  size_t page_faults;\n  mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);\n  _mi_fprintf(out, arg, \"%10s: %5zu.%03zu s\\n\", \"elapsed\", elapsed/1000, elapsed%1000);\n  _mi_fprintf(out, arg, \"%10s: user: %zu.%03zu s, system: %zu.%03zu s, faults: %zu, peak rss: \", \"process\",\n              user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, page_faults );\n  mi_printf_amount((int64_t)peak_rss, 1, out, arg, \"%s\");\n  if (peak_commit > 0) {\n    _mi_fprintf(out, arg, \", peak commit: \");\n    mi_printf_amount((int64_t)peak_commit, 1, out, arg, \"%s\");\n  }\n  _mi_fprintf(out, arg, \"\\n\");\n}\n\nstatic mi_msecs_t mi_process_start; // = 0\n\nstatic mi_stats_t* mi_stats_get_default(void) {\n  mi_heap_t* heap = mi_heap_get_default();\n  return &heap->tld->stats;\n}\n\nstatic void mi_stats_merge_from(mi_stats_t* stats) {\n  if (stats != &_mi_stats_main) {\n    mi_stats_add(&_mi_stats_main, stats);\n    memset(stats, 0, sizeof(mi_stats_t));\n  }\n}\n\nvoid mi_stats_reset(void) mi_attr_noexcept {\n  mi_stats_t* stats = mi_stats_get_default();\n  if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); }\n  memset(&_mi_stats_main, 0, sizeof(mi_stats_t));\n  if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); };\n}\n\nvoid mi_stats_merge(void) mi_attr_noexcept {\n  mi_stats_merge_from( mi_stats_get_default() );\n}\n\nvoid _mi_stats_merge_thread(mi_tld_t* tld) {\n  mi_stats_merge_from( &tld->stats );\n}\n\nvoid _mi_stats_done(mi_stats_t* stats) {  // called from `mi_thread_done`\n  mi_stats_merge_from(stats);\n}\n\nvoid mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {\n  mi_stats_merge_from(mi_stats_get_default());\n  _mi_stats_print(&_mi_stats_main, out, arg);\n}\n\nvoid mi_stats_print(void* out) mi_attr_noexcept {\n  // for compatibility there is an `out` parameter (which can be `stdout` or `stderr`)\n  mi_stats_print_out((mi_output_fun*)out, NULL);\n}\n\nvoid mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept {\n  _mi_stats_print(mi_stats_get_default(), out, arg);\n}\n\n\n// ----------------------------------------------------------------\n// Basic timer for convenience; use milli-seconds to avoid doubles\n// ----------------------------------------------------------------\n\nstatic mi_msecs_t mi_clock_diff;\n\nmi_msecs_t _mi_clock_now(void) {\n  return _mi_prim_clock_now();\n}\n\nmi_msecs_t _mi_clock_start(void) {\n  if (mi_clock_diff == 0.0) {\n    mi_msecs_t t0 = _mi_clock_now();\n    mi_clock_diff = _mi_clock_now() - t0;\n  }\n  return _mi_clock_now();\n}\n\nmi_msecs_t _mi_clock_end(mi_msecs_t start) {\n  mi_msecs_t end = _mi_clock_now();\n  return (end - start - mi_clock_diff);\n}\n\n\n// --------------------------------------------------------\n// Basic process statistics\n// --------------------------------------------------------\n\nmi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept\n{\n  mi_process_info_t pinfo;\n  _mi_memzero_var(pinfo);\n  pinfo.elapsed        = _mi_clock_end(mi_process_start);\n  pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current));\n  pinfo.peak_commit    = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak));\n  pinfo.current_rss    = pinfo.current_commit;\n  pinfo.peak_rss       = pinfo.peak_commit;\n  pinfo.utime          = 0;\n  pinfo.stime          = 0;\n  pinfo.page_faults    = 0;\n\n  _mi_prim_process_info(&pinfo);\n\n  if (elapsed_msecs!=NULL)  *elapsed_msecs  = (pinfo.elapsed < 0 ? 0 : (pinfo.elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.elapsed : PTRDIFF_MAX));\n  if (user_msecs!=NULL)     *user_msecs     = (pinfo.utime < 0 ? 0 : (pinfo.utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.utime : PTRDIFF_MAX));\n  if (system_msecs!=NULL)   *system_msecs   = (pinfo.stime < 0 ? 0 : (pinfo.stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.stime : PTRDIFF_MAX));\n  if (current_rss!=NULL)    *current_rss    = pinfo.current_rss;\n  if (peak_rss!=NULL)       *peak_rss       = pinfo.peak_rss;\n  if (current_commit!=NULL) *current_commit = pinfo.current_commit;\n  if (peak_commit!=NULL)    *peak_commit    = pinfo.peak_commit;\n  if (page_faults!=NULL)    *page_faults    = pinfo.page_faults;\n}\n\n\n// --------------------------------------------------------\n// Return statistics\n// --------------------------------------------------------\n\nbool mi_stats_get(mi_stats_t* stats) mi_attr_noexcept {\n  if (stats == NULL || stats->size != sizeof(mi_stats_t) || stats->version != MI_STAT_VERSION) return false;\n  _mi_memzero(stats,stats->size);\n  _mi_memcpy(stats, &_mi_stats_main, sizeof(mi_stats_t));\n  return true;\n}\n\n\n// --------------------------------------------------------\n// Statics in json format\n// --------------------------------------------------------\n\ntypedef struct mi_heap_buf_s {\n  char*   buf;\n  size_t  size;\n  size_t  used;\n  bool    can_realloc;\n} mi_heap_buf_t;\n\nstatic bool mi_heap_buf_expand(mi_heap_buf_t* hbuf) {\n  if (hbuf==NULL) return false;\n  if (hbuf->buf != NULL && hbuf->size>0) {\n    hbuf->buf[hbuf->size-1] = 0;\n  }\n  if (hbuf->size > SIZE_MAX/2 || !hbuf->can_realloc) return false;\n  const size_t newsize = (hbuf->size == 0 ? mi_good_size(12*MI_KiB) : 2*hbuf->size);\n  char* const  newbuf  = (char*)mi_rezalloc(hbuf->buf, newsize);\n  if (newbuf == NULL) return false;\n  hbuf->buf = newbuf;\n  hbuf->size = newsize;\n  return true;\n}\n\nstatic void mi_heap_buf_print(mi_heap_buf_t* hbuf, const char* msg) {\n  if (msg==NULL || hbuf==NULL) return;\n  if (hbuf->used + 1 >= hbuf->size && !hbuf->can_realloc) return;\n  for (const char* src = msg; *src != 0; src++) {\n    char c = *src;\n    if (hbuf->used + 1 >= hbuf->size) {\n      if (!mi_heap_buf_expand(hbuf)) return;\n    }\n    mi_assert_internal(hbuf->used < hbuf->size);\n    hbuf->buf[hbuf->used++] = c;\n  }\n  mi_assert_internal(hbuf->used < hbuf->size);\n  hbuf->buf[hbuf->used] = 0;\n}\n\nstatic void mi_heap_buf_print_count_bin(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, size_t bin, bool add_comma) {\n  const size_t binsize = _mi_bin_size(bin);\n  const size_t pagesize = (binsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_SMALL_PAGE_SIZE :\n                            (binsize <= MI_MEDIUM_OBJ_SIZE_MAX ? MI_MEDIUM_PAGE_SIZE :\n                              #if MI_LARGE_PAGE_SIZE\n                              (binsize <= MI_LARGE_OBJ_SIZE_MAX ? MI_LARGE_PAGE_SIZE : 0)\n                              #else\n                              0\n                              #endif\n                              ));\n  char buf[128];\n  _mi_snprintf(buf, 128, \"%s{ \\\"total\\\": %lld, \\\"peak\\\": %lld, \\\"current\\\": %lld, \\\"block_size\\\": %zu, \\\"page_size\\\": %zu }%s\\n\", prefix, stat->total, stat->peak, stat->current, binsize, pagesize, (add_comma ? \",\" : \"\"));\n  buf[127] = 0;\n  mi_heap_buf_print(hbuf, buf);\n}\n\nstatic void mi_heap_buf_print_count(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, bool add_comma) {\n  char buf[128];\n  _mi_snprintf(buf, 128, \"%s{ \\\"total\\\": %lld, \\\"peak\\\": %lld, \\\"current\\\": %lld }%s\\n\", prefix, stat->total, stat->peak, stat->current, (add_comma ? \",\" : \"\"));\n  buf[127] = 0;\n  mi_heap_buf_print(hbuf, buf);\n}\n\nstatic void mi_heap_buf_print_count_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_count_t* stat) {\n  char buf[128];\n  _mi_snprintf(buf, 128, \"  \\\"%s\\\": \", name);\n  buf[127] = 0;\n  mi_heap_buf_print(hbuf, buf);\n  mi_heap_buf_print_count(hbuf, \"\", stat, true);\n}\n\nstatic void mi_heap_buf_print_value(mi_heap_buf_t* hbuf, const char* name, int64_t val) {\n  char buf[128];\n  _mi_snprintf(buf, 128, \"  \\\"%s\\\": %lld,\\n\", name, val);\n  buf[127] = 0;\n  mi_heap_buf_print(hbuf, buf);\n}\n\nstatic void mi_heap_buf_print_size(mi_heap_buf_t* hbuf, const char* name, size_t val, bool add_comma) {\n  char buf[128];\n  _mi_snprintf(buf, 128, \"    \\\"%s\\\": %zu%s\\n\", name, val, (add_comma ? \",\" : \"\"));\n  buf[127] = 0;\n  mi_heap_buf_print(hbuf, buf);\n}\n\nstatic void mi_heap_buf_print_counter_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_counter_t* stat) {\n  mi_heap_buf_print_value(hbuf, name, stat->total);\n}\n\n#define MI_STAT_COUNT(stat)    mi_heap_buf_print_count_value(&hbuf, #stat, &stats->stat);\n#define MI_STAT_COUNTER(stat)  mi_heap_buf_print_counter_value(&hbuf, #stat, &stats->stat);\n\nchar* mi_stats_get_json(size_t output_size, char* output_buf) mi_attr_noexcept {\n  mi_heap_buf_t hbuf = { NULL, 0, 0, true };\n  if (output_size > 0 && output_buf != NULL) {\n    _mi_memzero(output_buf, output_size);\n    hbuf.buf = output_buf;\n    hbuf.size = output_size;\n    hbuf.can_realloc = false;\n  }\n  else {\n    if (!mi_heap_buf_expand(&hbuf)) return NULL;\n  }\n  mi_heap_buf_print(&hbuf, \"{\\n\");\n  mi_heap_buf_print_value(&hbuf, \"stat_version\", MI_STAT_VERSION);\n  mi_heap_buf_print_value(&hbuf, \"mimalloc_version\", MI_MALLOC_VERSION);\n\n  // process info\n  mi_heap_buf_print(&hbuf, \"  \\\"process\\\": {\\n\");\n  size_t elapsed;\n  size_t user_time;\n  size_t sys_time;\n  size_t current_rss;\n  size_t peak_rss;\n  size_t current_commit;\n  size_t peak_commit;\n  size_t page_faults;\n  mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);\n  mi_heap_buf_print_size(&hbuf, \"elapsed_msecs\", elapsed, true);\n  mi_heap_buf_print_size(&hbuf, \"user_msecs\", user_time, true);\n  mi_heap_buf_print_size(&hbuf, \"system_msecs\", sys_time, true);\n  mi_heap_buf_print_size(&hbuf, \"page_faults\", page_faults, true);\n  mi_heap_buf_print_size(&hbuf, \"rss_current\", current_rss, true);\n  mi_heap_buf_print_size(&hbuf, \"rss_peak\", peak_rss, true);\n  mi_heap_buf_print_size(&hbuf, \"commit_current\", current_commit, true);\n  mi_heap_buf_print_size(&hbuf, \"commit_peak\", peak_commit, false);\n  mi_heap_buf_print(&hbuf, \"  },\\n\");\n\n  // statistics\n  mi_stats_t* stats = &_mi_stats_main;\n  MI_STAT_FIELDS()\n\n  // size bins\n  mi_heap_buf_print(&hbuf, \"  \\\"malloc_bins\\\": [\\n\");\n  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {\n    mi_heap_buf_print_count_bin(&hbuf, \"    \", &stats->malloc_bins[i], i, i!=MI_BIN_HUGE);\n  }\n  mi_heap_buf_print(&hbuf, \"  ],\\n\");\n  mi_heap_buf_print(&hbuf, \"  \\\"page_bins\\\": [\\n\");\n  for (size_t i = 0; i <= MI_BIN_HUGE; i++) {\n    mi_heap_buf_print_count_bin(&hbuf, \"    \", &stats->page_bins[i], i, i!=MI_BIN_HUGE);\n  }\n  mi_heap_buf_print(&hbuf, \"  ]\\n\");\n  mi_heap_buf_print(&hbuf, \"}\\n\");\n  if (hbuf.used >= hbuf.size) {\n    // failed\n    if (hbuf.can_realloc) { mi_free(hbuf.buf); }\n    return NULL;\n  }\n  else {\n    return hbuf.buf;\n  }\n}\n"
  },
  {
    "path": "test/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18)\nproject(mimalloc-test C CXX)\n\nset(CMAKE_C_STANDARD 11)\nset(CMAKE_CXX_STANDARD 17)\n\n# Set default build type\nif (NOT CMAKE_BUILD_TYPE)\n  if (\"${CMAKE_BINARY_DIR}\" MATCHES \".*(D|d)ebug$\")\n    message(STATUS \"No build type selected, default to *** Debug ***\")\n    set(CMAKE_BUILD_TYPE \"Debug\")\n  else()\n    message(STATUS \"No build type selected, default to *** Release ***\")\n    set(CMAKE_BUILD_TYPE \"Release\")\n  endif()\nendif()\n\n# Import mimalloc (if installed)\nfind_package(mimalloc CONFIG REQUIRED)\nmessage(STATUS \"Found mimalloc installed at: ${MIMALLOC_LIBRARY_DIR} (${MIMALLOC_VERSION_DIR})\")\n\n\n# link with a dynamic shared library\n# use `LD_PRELOAD` to actually override malloc/free at runtime with mimalloc\nadd_executable(dynamic-override  main-override.c)\ntarget_link_libraries(dynamic-override PUBLIC mimalloc)\n\nadd_executable(dynamic-override-cxx  main-override.cpp)\ntarget_link_libraries(dynamic-override-cxx PUBLIC mimalloc)\n\n\n# overriding with a static object file works reliable as the symbols in the\n# object file have priority over those in library files\nadd_executable(static-override-obj main-override.c ${MIMALLOC_OBJECT_DIR}/mimalloc${CMAKE_C_OUTPUT_EXTENSION})\ntarget_include_directories(static-override-obj PUBLIC ${MIMALLOC_INCLUDE_DIR})\ntarget_link_libraries(static-override-obj PUBLIC mimalloc-static)\n\n\n# overriding with a static library works too if using the `mimalloc-override.h`\n# header to redefine malloc/free. (the library already overrides new/delete)\nadd_executable(static-override-static main-override-static.c)\ntarget_link_libraries(static-override-static PUBLIC mimalloc-static)\n\n\n# overriding with a static library: this may not work if the library is linked too late\n# on the command line after the C runtime library; but we cannot control that well in CMake\nadd_executable(static-override main-override.c)\ntarget_link_libraries(static-override PUBLIC mimalloc-static)\n\nadd_executable(static-override-cxx  main-override.cpp)\ntarget_link_libraries(static-override-cxx PUBLIC mimalloc-static)\n\n\n## test memory errors\nadd_executable(test-wrong  test-wrong.c)\ntarget_link_libraries(test-wrong PUBLIC mimalloc)\n"
  },
  {
    "path": "test/main-override-dep.cpp",
    "content": "// Issue #981: test overriding allocation in a DLL that is compiled independent of mimalloc. \n// This is imported by the `mimalloc-test-override` project.\n#include <string>\n#include <iostream>\n#include \"main-override-dep.h\"\n\nstd::string TestAllocInDll::GetString()\n{\n\tchar* test = new char[128];\n\tmemset(test, 0, 128);\n\tconst char* t = \"test\";\n\tmemcpy(test, t, 4);\n\tstd::string r = test;\n  std::cout << \"override-dep: GetString: \" << r << \"\\n\";\n\tdelete[] test;\n\treturn r;\n}\n\n\nclass Static {\nprivate:\n  void* p;\npublic:\n  Static() {\n    printf(\"override-dep: static constructor\\n\");\n    p = malloc(64);\n    return;\n  }\n  ~Static() {\n    free(p);\n    printf(\"override-dep: static destructor\\n\");\n    return;\n  }\n};\n\nstatic Static s = Static();\n\n\n#include <windows.h>\n\nBOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID reserved) {\n  (void)(reserved);\n  (void)(module);\n  if (reason==DLL_PROCESS_ATTACH) {\n    printf(\"override-dep: dll attach\\n\");\n  }\n  else if (reason==DLL_PROCESS_DETACH) {\n    printf(\"override-dep: dll detach\\n\");\n  }  \n  return TRUE;\n}\n"
  },
  {
    "path": "test/main-override-dep.h",
    "content": "#pragma once\n// Issue #981: test overriding allocation in a DLL that is compiled independent of mimalloc. \n// This is imported by the `mimalloc-test-override` project.\n\n#include <string>\n\nclass TestAllocInDll\n{\npublic:\n\t__declspec(dllexport) std::string GetString();\n};\n"
  },
  {
    "path": "test/main-override-static.c",
    "content": "#if _WIN32\n#include <windows.h>\n#endif\n#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n#include <stdint.h>\n\n#include <mimalloc.h>\n#include <mimalloc-override.h>  // redefines malloc etc.\n\n\nstatic void double_free1();\nstatic void double_free2();\nstatic void corrupt_free();\nstatic void block_overflow1();\nstatic void block_overflow2();\nstatic void invalid_free();\nstatic void test_aslr(void);\nstatic void test_process_info(void);\nstatic void test_reserved(void);\nstatic void negative_stat(void);\nstatic void alloc_huge(void);\nstatic void test_heap_walk(void);\nstatic void test_heap_arena(void);\nstatic void test_align(void);\nstatic void test_canary_leak(void);\nstatic void test_manage_os_memory(void);\n// static void test_large_pages(void);\n\nint main() {\n  mi_version();\n  mi_stats_reset();\n  \n  // mi_bins();\n\n  // test_manage_os_memory();\n  // test_large_pages();\n  // detect double frees and heap corruption\n  // double_free1();\n  // double_free2();\n  // corrupt_free();\n  // block_overflow1();\n  // block_overflow2();\n  test_canary_leak();\n  // test_aslr();\n  // invalid_free();\n  // test_reserved();\n  // negative_stat();\n  // test_heap_walk();\n  // alloc_huge();\n  // test_heap_walk();\n  // test_heap_arena();\n  // test_align();\n  \n  void* p1 = malloc(78);\n  void* p2 = malloc(24);\n  free(p1);\n  p1 = mi_malloc(8);\n  char* s = strdup(\"hello\\n\");\n  free(p2);\n\n  mi_heap_t* h = mi_heap_new();\n  mi_heap_set_default(h);\n\n  p2 = malloc(16);\n  p1 = realloc(p1, 32);\n  free(p1);\n  free(p2);\n  free(s);\n  \n  /* now test if override worked by allocating/freeing across the api's*/\n  //p1 = mi_malloc(32);\n  //free(p1);\n  //p2 = malloc(32);\n  //mi_free(p2);\n\n  //mi_collect(true);\n  //mi_stats_print(NULL);\n\n  // test_process_info();\n\n  return 0;\n}\n\nstatic void test_align() {\n  void* p = mi_malloc_aligned(256, 256);\n  if (((uintptr_t)p % 256) != 0) {\n    fprintf(stderr, \"%p is not 256 alignend!\\n\", p);\n  }\n}\n\nstatic void invalid_free() {\n  free((void*)0xBADBEEF);\n  realloc((void*)0xBADBEEF,10);\n}\n\nstatic void block_overflow1() {\n  uint8_t* p = (uint8_t*)mi_malloc(17);\n  p[18] = 0;\n  free(p);\n}\n\nstatic void block_overflow2() {\n  uint8_t* p = (uint8_t*)mi_malloc(16);\n  p[17] = 0;\n  free(p);\n}\n\n// The double free samples come ArcHeap [1] by Insu Yun (issue #161)\n// [1]: https://arxiv.org/pdf/1903.00503.pdf\n\nstatic void double_free1() {\n  void* p[256];\n  //uintptr_t buf[256];\n\n  p[0] = mi_malloc(622616);\n  p[1] = mi_malloc(655362);\n  p[2] = mi_malloc(786432);\n  mi_free(p[2]);\n  // [VULN] Double free\n  mi_free(p[2]);\n  p[3] = mi_malloc(786456);\n  // [BUG] Found overlap\n  // p[3]=0x429b2ea2000 (size=917504), p[1]=0x429b2e42000 (size=786432)\n  fprintf(stderr, \"p3: %p-%p, p1: %p-%p, p2: %p\\n\", p[3], (uint8_t*)(p[3]) + 786456, p[1], (uint8_t*)(p[1]) + 655362, p[2]);\n}\n\nstatic void double_free2() {\n  void* p[256];\n  //uintptr_t buf[256];\n  // [INFO] Command buffer: 0x327b2000\n  // [INFO] Input size: 182\n  p[0] = malloc(712352);\n  p[1] = malloc(786432);\n  free(p[0]);\n  // [VULN] Double free\n  free(p[0]);\n  p[2] = malloc(786440);\n  p[3] = malloc(917504);\n  p[4] = malloc(786440);\n  // [BUG] Found overlap\n  // p[4]=0x433f1402000 (size=917504), p[1]=0x433f14c2000 (size=786432)\n  fprintf(stderr, \"p1: %p-%p, p2: %p-%p\\n\", p[4], (uint8_t*)(p[4]) + 917504, p[1], (uint8_t*)(p[1]) + 786432);\n}\n\n\n// Try to corrupt the heap through buffer overflow\n#define N   256\n#define SZ  64\n\nstatic void corrupt_free() {\n  void* p[N];\n  // allocate\n  for (int i = 0; i < N; i++) {\n    p[i] = malloc(SZ);\n  }\n  // free some\n  for (int i = 0; i < N; i += (N/10)) {\n    free(p[i]);\n    p[i] = NULL;\n  }\n  // try to corrupt the free list\n  for (int i = 0; i < N; i++) {\n    if (p[i] != NULL) {\n      memset(p[i], 0, SZ+8);\n    }\n  }\n  // allocate more.. trying to trigger an allocation from a corrupted entry\n  // this may need many allocations to get there (if at all)\n  for (int i = 0; i < 4096; i++) {\n    malloc(SZ);\n  }\n}\n\nstatic void test_aslr(void) {\n  void* p[256];\n  p[0] = malloc(378200);\n  p[1] = malloc(1134626);\n  printf(\"p1: %p, p2: %p\\n\", p[0], p[1]);\n}\n\nstatic void test_process_info(void) {\n  size_t elapsed = 0;\n  size_t user_msecs = 0;\n  size_t system_msecs = 0;\n  size_t current_rss = 0;\n  size_t peak_rss = 0;\n  size_t current_commit = 0;\n  size_t peak_commit = 0;\n  size_t page_faults = 0;\n  for (int i = 0; i < 100000; i++) {\n    void* p = calloc(100,10);\n    free(p);\n  }\n  mi_process_info(&elapsed, &user_msecs, &system_msecs, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);\n  printf(\"\\n\\n*** process info: elapsed %3zd.%03zd s, user: %3zd.%03zd s, rss: %zd b, commit: %zd b\\n\\n\", elapsed/1000, elapsed%1000, user_msecs/1000, user_msecs%1000, peak_rss, peak_commit);\n}\n\nstatic void test_reserved(void) {\n#define KiB 1024ULL\n#define MiB (KiB*KiB)\n#define GiB (MiB*KiB)\n  mi_reserve_os_memory(4*GiB, false, true);\n  void* p1 = malloc(100);\n  void* p2 = malloc(100000);\n  void* p3 = malloc(2*GiB);\n  void* p4 = malloc(1*GiB + 100000);\n  free(p1);\n  free(p2);\n  free(p3);\n  p3 = malloc(1*GiB);\n  free(p4);\n}\n\n\n\nstatic void negative_stat(void) {\n  int* p = mi_malloc(60000);\n  mi_stats_print_out(NULL, NULL);\n  *p = 100;\n  mi_free(p);\n  mi_stats_print_out(NULL, NULL);\n}\n\nstatic void alloc_huge(void) {\n  void* p = mi_malloc(67108872);\n  mi_free(p);\n}\n\nstatic bool test_visit(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg) {\n  if (block == NULL) {\n    printf(\"visiting an area with blocks of size %zu (including padding)\\n\", area->full_block_size);\n  }\n  else {\n    printf(\"  block of size %zu (allocated size is %zu)\\n\", block_size, mi_usable_size(block));\n  }\n  return true;\n}\n\nstatic void test_heap_walk(void) {\n  mi_heap_t* heap = mi_heap_new();\n  mi_heap_malloc(heap, 16*2097152);\n  mi_heap_malloc(heap, 2067152);\n  mi_heap_malloc(heap, 2097160);\n  mi_heap_malloc(heap, 24576);\n  mi_heap_visit_blocks(heap, true, &test_visit, NULL);\n}\n\nstatic void test_heap_arena(void) {\n  mi_arena_id_t arena_id;\n  int err = mi_reserve_os_memory_ex(100 * 1024 * 1024, false /* commit */, false /* allow large */, true /* exclusive */, &arena_id);\n  if (err) abort();\n  mi_heap_t* heap = mi_heap_new_in_arena(arena_id);\n  for (int i = 0; i < 500000; i++) {\n    void* p = mi_heap_malloc(heap, 1024);\n    if (p == NULL) {\n      printf(\"out of memory after %d kb (expecting about 100_000kb)\\n\", i);\n      break;\n    }\n  }\n}\n\nstatic void test_canary_leak(void) {\n  char* p = mi_mallocn_tp(char,23);\n  for(int i = 0; i < 23; i++) {\n    p[i] = '0'+i;\n  }\n  puts(p);\n  free(p);\n}\n\n#if _WIN32\nstatic void test_manage_os_memory(void) {\n  size_t size = 256 * 1024 * 1024;\n  void* ptr = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); \n  mi_arena_id_t arena_id;\n  mi_manage_os_memory_ex(ptr, size, true /* committed */, true /* pinned */, false /* is zero */, -1 /* numa node */, true /* exclusive */, &arena_id);\n  mi_heap_t* cuda_heap = mi_heap_new_in_arena(arena_id);    // you can do this in any thread\n\n  // now allocate only in the cuda arena\n  void* p1 = mi_heap_malloc(cuda_heap, 8);\n  int* p2 = mi_heap_malloc_tp(cuda_heap, int);\n  *p2 = 42;\n  \n  // and maybe set the cuda heap as the default heap? (but careful as now `malloc` will allocate in the cuda heap as well)\n  {\n    mi_heap_t* prev_default_heap = mi_heap_set_default(cuda_heap);\n    void* p3 = mi_malloc(8);  // allocate in the cuda heap \n    mi_free(p3);\n  }\n  mi_free(p1);\n  mi_free(p2);\n}\n#else\nstatic void test_manage_os_memory(void) {\n  // empty\n}\n#endif\n\n// Experiment with huge OS pages\n#if 0\n\n#include <mimalloc/types.h>\n#include <mimalloc/internal.h>\n#include <unistd.h>\n#include <sys/mman.h>\n\nstatic void test_large_pages(void) {\n  mi_memid_t memid;\n\n  #if 0\n  size_t pages_reserved;\n  size_t page_size;\n  uint8_t* p = (uint8_t*)_mi_os_alloc_huge_os_pages(1, -1, 30000, &pages_reserved, &page_size, &memid);\n  const size_t req_size = pages_reserved * page_size;\n  #else\n  const size_t req_size = 64*MI_MiB;\n  uint8_t* p = (uint8_t*)_mi_os_alloc(req_size,&memid,NULL);\n  #endif\n\n  p[0] = 1;\n\n  //_mi_os_protect(p, _mi_os_page_size());\n  //_mi_os_unprotect(p, _mi_os_page_size());\n  //_mi_os_decommit(p, _mi_os_page_size(), NULL);\n  if (madvise(p, req_size, MADV_HUGEPAGE) == 0) {\n    printf(\"advised huge pages\\n\");\n    _mi_os_decommit(p, _mi_os_page_size(), NULL);\n  };\n  _mi_os_free(p, req_size, memid, NULL);\n}\n\n#endif\n\n// ----------------------------\n// bin size experiments\n// ------------------------------\n\n#if 0\n#include <stdint.h>\n#include <stdbool.h>\n\n#define MI_INTPTR_SIZE 8\n#define MI_LARGE_WSIZE_MAX (4*1024*1024 / MI_INTPTR_SIZE)\n\n#define MI_BIN_HUGE 100\n//#define MI_ALIGN2W\n\n// Bit scan reverse: return the index of the highest bit.\nstatic inline uint8_t mi_bsr32(uint32_t x);\n\n#if defined(_MSC_VER)\n//#include <Windows.h>\n#include <intrin.h>\nstatic inline uint8_t mi_bsr32(uint32_t x) {\n  uint32_t idx;\n  _BitScanReverse(&idx, x);\n  return idx;\n}\n#elif defined(__GNUC__) || defined(__clang__)\nstatic inline uint8_t mi_bsr32(uint32_t x) {\n  return (31 - __builtin_clz(x));\n}\n#else\nstatic inline uint8_t mi_bsr32(uint32_t x) {\n  // de Bruijn multiplication, see <http://supertech.csail.mit.edu/papers/debruijn.pdf>\n  static const uint8_t debruijn[32] = {\n     31,  0, 22,  1, 28, 23, 18,  2, 29, 26, 24, 10, 19,  7,  3, 12,\n     30, 21, 27, 17, 25,  9,  6, 11, 20, 16,  8,  5, 15,  4, 14, 13,\n  };\n  x |= x >> 1;\n  x |= x >> 2;\n  x |= x >> 4;\n  x |= x >> 8;\n  x |= x >> 16;\n  x++;\n  return debruijn[(x*0x076be629) >> 27];\n}\n#endif\n\n\n// Bit scan reverse: return the index of the highest bit.\nuint8_t _mi_bsr(uintptr_t x) {\n  if (x == 0) return 0;\n  #if MI_INTPTR_SIZE==8\n  uint32_t hi = (x >> 32);\n  return (hi == 0 ? mi_bsr32((uint32_t)x) : 32 + mi_bsr32(hi));\n  #elif MI_INTPTR_SIZE==4\n  return mi_bsr32(x);\n  #else\n  # error \"define bsr for non-32 or 64-bit platforms\"\n  #endif\n}\n\n\n\nstatic inline size_t _mi_wsize_from_size(size_t size) {\n  return (size + sizeof(uintptr_t) - 1) / sizeof(uintptr_t);\n}\n\n// #define MI_ALIGN2W\n\n// Return the bin for a given field size.\n// Returns MI_BIN_HUGE if the size is too large.\n// We use `wsize` for the size in \"machine word sizes\",\n// i.e. byte size == `wsize*sizeof(void*)`.\nstatic inline size_t mi_bin(size_t wsize) {\n  // size_t wsize = _mi_wsize_from_size(size);\n  // size_t bin;\n  /*if (wsize <= 1) {\n    bin = 1;\n  }\n  */\n#if defined(MI_ALIGN4W)\n  if (wsize <= 4) {\n    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes\n  }\n#elif defined(MI_ALIGN2W)\n  if (wsize <= 8) {\n    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes\n  }\n#else\n  if (wsize <= 8) {\n    return (wsize == 0 ? 1 : wsize);\n  }\n#endif\n  else if (wsize > MI_LARGE_WSIZE_MAX) {\n    return MI_BIN_HUGE;\n  }\n  else {\n#if defined(MI_ALIGN4W)\n    if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes\n#endif\n    wsize--;\n    // find the highest bit\n    const size_t b = _mi_bsr(wsize);  // note: wsize != 0\n    // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation).\n    // - adjust with 3 because we use do not round the first 8 sizes\n    //   which each get an exact bin\n    const size_t bin = ((b << 2) + ((wsize >> (b - 2)) & 0x03)) - 3;\n    assert(bin > 0 && bin < MI_BIN_HUGE);\n    return bin;\n  }\n}\n\n\nstatic inline uint8_t _mi_bin4(size_t size) {\n  size_t wsize = _mi_wsize_from_size(size);\n  uint8_t bin;\n  if (wsize <= 1) {\n    bin = 1;\n  }\n#if defined(MI_ALIGN4W)\n  else if (wsize <= 4) {\n    bin = (uint8_t)((wsize+1)&~1); // round to double word sizes\n  }\n#elif defined(MI_ALIGN2W)\n  else if (wsize <= 8) {\n    bin = (uint8_t)((wsize+1)&~1); // round to double word sizes\n  }\n#else\n  else if (wsize <= 8) {\n    bin = (uint8_t)wsize;\n  }\n#endif\n  else if (wsize > MI_LARGE_WSIZE_MAX) {\n    bin = MI_BIN_HUGE;\n  }\n  else {\n    uint8_t b = mi_bsr32((uint32_t)wsize);\n    bin = ((b << 1) + (uint8_t)((wsize >> (b - 1)) & 0x01)) + 3;\n  }\n  return bin;\n}\n\nstatic size_t _mi_binx4(size_t wsize) {\n  size_t bin;\n  if (wsize <= 1) {\n    bin = 1;\n  }\n  else if (wsize <= 8) {\n    // bin = (wsize+1)&~1; // round to double word sizes\n    bin = (uint8_t)wsize;\n  }\n  else {\n    uint8_t b = mi_bsr32((uint32_t)wsize);\n    if (b <= 1) return wsize;\n    bin = ((b << 1) | (wsize >> (b - 1))&0x01) + 3;\n  }\n  return bin;\n}\n\nstatic size_t _mi_binx8(size_t bsize) {\n  if (bsize<=1) return bsize;\n  uint8_t b = mi_bsr32((uint32_t)bsize);\n  if (b <= 2) return bsize;\n  size_t bin = ((b << 2) | (bsize >> (b - 2))&0x03) - 5;\n  return bin;\n}\n\n\nstatic inline size_t mi_binx(size_t wsize) {\n  uint8_t bin;\n  if (wsize <= 1) {\n    bin = 1;\n  }\n  else if (wsize <= 8) {\n    // bin = (wsize+1)&~1; // round to double word sizes\n    bin = (uint8_t)wsize;\n  }\n  else {\n    wsize--;\n    // find the highest bit\n    uint8_t b = (uint8_t)mi_bsr32((uint32_t)wsize);  // note: wsize != 0\n    // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation).\n    // - adjust with 3 because we use do not round the first 8 sizes\n    //   which each get an exact bin\n    bin = ((b << 2) + (uint8_t)((wsize >> (b - 2)) & 0x03)) - 3;\n  }\n  return bin;\n}\n\n\nstatic void mi_bins(void) {\n  //printf(\"  QNULL(1), /* 0 */ \\\\\\n  \");\n  size_t last_bin = 0;\n  for (size_t wsize = 1; wsize <= (4*1024*1024) / 8 + 1024; wsize++) {\n    size_t bin = mi_bin(wsize);\n    if (bin != last_bin) {\n      //printf(\"min bsize: %6zd, max bsize: %6zd, bin: %6zd\\n\", min_wsize, last_wsize, last_bin);\n      printf(\"QNULL(%6zd), \", wsize-1);\n      if (last_bin%8 == 0) printf(\"/* %zu */ \\\\\\n  \", last_bin);\n      last_bin = bin;\n    }\n  }\n}\n#endif\n"
  },
  {
    "path": "test/main-override.c",
    "content": "#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n\n#include <mimalloc-override.h>\n\nint main() {\n  mi_version();       // ensure mimalloc library is linked\n  void* p1 = malloc(78);\n  void* p2 = malloc(24);\n  free(p1);\n  p1 = malloc(8);\n  //char* s = strdup(\"hello\\n\");\n  free(p2);\n  p2 = malloc(16);\n  p1 = realloc(p1, 32);\n  free(p1);\n  free(p2);\n  //free(s);\n  //mi_collect(true);\n\n  /* now test if override worked by allocating/freeing across the api's*/\n  //p1 = mi_malloc(32);\n  //free(p1);\n  //p2 = malloc(32);\n  //mi_free(p2);\n  p1 = malloc(24);\n  p2 = reallocarray(p1, 16, 16);\n  free(p2);\n  p1 = malloc(24);\n  assert(reallocarr(&p1, 16, 16) == 0);\n  free(p1);\n  mi_stats_print(NULL);\n  return 0;\n}\n"
  },
  {
    "path": "test/main-override.cpp",
    "content": "#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n#include <stdint.h>\n\n#include <mimalloc.h>\n#include <new>\n#include <vector>\n#include <future>\n#include <iostream>\n#include <thread>\n#include <random>\n#include <chrono>\n#include <assert.h>\n\n#ifdef _WIN32\n#include <mimalloc-new-delete.h>\n#include <windows.h>\nstatic void msleep(unsigned long msecs) { Sleep(msecs); }\n#else\n#include <unistd.h>\nstatic void msleep(unsigned long msecs) { usleep(msecs * 1000UL); }\n#endif\n\nstatic void heap_thread_free_large(); // issue #221\nstatic void heap_no_delete();         // issue #202\nstatic void heap_late_free();         // issue #204\nstatic void padding_shrink();         // issue #209\nstatic void various_tests();\nstatic void test_mt_shutdown();\nstatic void large_alloc(void);        // issue #363\nstatic void fail_aslr();              // issue #372\nstatic void tsan_numa_test();         // issue #414\nstatic void strdup_test();            // issue #445\nstatic void bench_alloc_large(void);  // issue #xxx\n//static void test_large_migrate(void); // issue #691\nstatic void heap_thread_free_huge();\nstatic void test_std_string();        // issue #697\nstatic void test_thread_local();      // issue #944\n// static void test_mixed0();             // issue #942\nstatic void test_mixed1();             // issue #942\nstatic void test_stl_allocators();\nstatic void test_thread_leak(void);   // issue #1104\nstatic void test_perf(void);          // issue #1104\nstatic void test_perf2(void);         // issue #1104\nstatic void test_perf3(void);         // issue #1104\nstatic void test_perf4(void);         // issue #1104\nstatic void test_perf5(void);         // issue #1104\n\n#if _WIN32\n#include \"main-override-dep.h\"\nstatic void test_dep();               // issue #981: test overriding in another DLL\n#else\nstatic void test_dep() { };\n#endif\n\nint main() {\n  mi_stats_reset();  // ignore earlier allocations\n  // various_tests();\n  // test_mixed1();\n  // test_dep();\n\n  // test_thread_leak();\n  // test_perf();\n  // test_perf2();\n  // test_perf3();\n  // test_perf4();\n  test_perf5();\n\n  //test_std_string();\n  //test_thread_local();\n  // heap_thread_free_huge();\n  /*\n   heap_thread_free_huge();\n   heap_thread_free_large();\n   heap_no_delete();\n   heap_late_free();\n   padding_shrink();\n   various_tests();\n   large_alloc();\n   tsan_numa_test();\n   strdup_test();\n  */\n  // test_stl_allocators();\n  // test_mt_shutdown();\n  // test_large_migrate();\n\n  //fail_aslr();\n  mi_stats_print(NULL);\n  return 0;\n}\n\nstatic void* p = malloc(8);\n\nvoid free_p() {\n  free(p);\n  return;\n}\n\nclass Test {\nprivate:\n  int i;\npublic:\n  Test(int x) { i = x; }\n  ~Test() { }\n};\n\n\nstatic void various_tests() {\n  atexit(free_p);\n  void* p1 = malloc(78);\n  void* p2 = mi_malloc_aligned(24, 16);\n  free(p1);\n  p1 = malloc(8);\n  char* s = mi_strdup(\"hello\\n\");\n\n  mi_free(p2);\n  p2 = malloc(16);\n  p1 = realloc(p1, 32);\n  free(p1);\n  free(p2);\n  mi_free(s);\n\n  Test* t = new Test(42);\n  delete t;\n  t = new (std::nothrow) Test(42);\n  delete t;\n  auto tbuf = new unsigned char[sizeof(Test)];\n  t = new (tbuf) Test(42);\n  t->~Test();\n  delete[] tbuf;\n\n  #if _WIN32\n  const char* ptr = ::_Getdays();  // test _base overrid\n  free((void*)ptr);\n  #endif\n}\n\nclass Static {\nprivate:\n  void* p;\npublic:\n  Static() {\n    p = malloc(64);\n    return;\n  }\n  ~Static() {\n    free(p);\n    return;\n  }\n};\n\nstatic Static s = Static();\n\n\nstatic bool test_stl_allocator1() {\n  std::vector<int, mi_stl_allocator<int> > vec;\n  vec.push_back(1);\n  vec.pop_back();\n  return vec.size() == 0;\n}\n\nstruct some_struct { int i; int j; double z; };\n\n\n#if _WIN32\nstatic void test_dep()\n{\n  TestAllocInDll t;\n  std::string s = t.GetString();\n  std::cout << \"test_dep GetString: \" << s << \"\\n\";\n}\n#endif\n\n\nstatic bool test_stl_allocator2() {\n  std::vector<some_struct, mi_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n}\n\n#if MI_HAS_HEAP_STL_ALLOCATOR\nstatic bool test_stl_allocator3() {\n  std::vector<int, mi_heap_stl_allocator<int> > vec;\n  vec.push_back(1);\n  vec.pop_back();\n  return vec.size() == 0;\n}\n\nstatic bool test_stl_allocator4() {\n  std::vector<some_struct, mi_heap_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n}\n\nstatic bool test_stl_allocator5() {\n  std::vector<int, mi_heap_destroy_stl_allocator<int> > vec;\n  vec.push_back(1);\n  vec.pop_back();\n  return vec.size() == 0;\n}\n\nstatic bool test_stl_allocator6() {\n  std::vector<some_struct, mi_heap_destroy_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n}\n#endif\n\nstatic void test_stl_allocators() {\n  test_stl_allocator1();\n  test_stl_allocator2();\n#if MI_HAS_HEAP_STL_ALLOCATOR\n  test_stl_allocator3();\n  test_stl_allocator4();\n  test_stl_allocator5();\n  test_stl_allocator6();\n#endif\n}\n\n#if 0\n#include <algorithm>\n#include <chrono>\n#include <functional>\n#include <iostream>\n#include <thread>\n#include <vector>\n\nstatic void test_mixed0() {\n    std::vector<std::unique_ptr<std::size_t>> numbers(1024 * 1024 * 100);\n    std::vector<std::thread> threads(1);\n\n    std::atomic<std::size_t> index{};\n\n    auto start = std::chrono::system_clock::now();\n\n    for (auto& thread : threads) {\n        thread = std::thread{[&index, &numbers]() {\n            while (true) {\n                auto i = index.fetch_add(1, std::memory_order_relaxed);\n                if (i >= numbers.size()) return;\n\n                numbers[i] = std::make_unique<std::size_t>(i);\n            }\n        }};\n    }\n\n    for (auto& thread : threads) thread.join();\n\n    auto end = std::chrono::system_clock::now();\n\n    auto duration =\n        std::chrono::duration_cast<std::chrono::milliseconds>(end - start);\n    std::cout << \"Running on \" << threads.size() << \" threads took \" << duration\n              << std::endl;\n}\n#endif\n\nvoid asd() {\n  void* p = malloc(128);\n  free(p);\n}\nstatic void test_mixed1() {\n    std::thread thread(asd);\n    thread.join();\n}\n\n#if 0\n// issue #691\nstatic char* cptr;\n\nstatic void* thread1_allocate()\n{\n  cptr = mi_calloc_tp(char,22085632);\n  return NULL;\n}\n\nstatic void* thread2_free()\n{\n  assert(cptr);\n  mi_free(cptr);\n  cptr = NULL;\n  return NULL;\n}\n\nstatic void test_large_migrate(void) {\n  auto t1 = std::thread(thread1_allocate);\n  t1.join();\n  auto t2 = std::thread(thread2_free);\n  t2.join();\n  /*\n  pthread_t thread1, thread2;\n\n  pthread_create(&thread1, NULL, &thread1_allocate, NULL);\n  pthread_join(thread1, NULL);\n\n  pthread_create(&thread2, NULL, &thread2_free, NULL);\n  pthread_join(thread2, NULL);\n  */\n  return;\n}\n#endif\n\n// issue 445\nstatic void strdup_test() {\n#ifdef _MSC_VER\n  char* s = _strdup(\"hello\\n\");\n  char* buf = NULL;\n  size_t len;\n  _dupenv_s(&buf, &len, \"MIMALLOC_VERBOSE\");\n  mi_free(buf);\n  mi_free(s);\n#endif\n}\n\n// Issue #202\nstatic void heap_no_delete_worker() {\n  mi_heap_t* heap = mi_heap_new();\n  void* q = mi_heap_malloc(heap, 1024); (void)(q);\n  // mi_heap_delete(heap); // uncomment to prevent assertion\n}\n\nstatic void heap_no_delete() {\n  auto t1 = std::thread(heap_no_delete_worker);\n  t1.join();\n}\n\n\n// Issue #697\nstatic void test_std_string() {\n  std::string path = \"/Users/xxxx/Library/Developer/Xcode/DerivedData/xxxxxxxxxx/Build/Intermediates.noindex/xxxxxxxxxxx/arm64/XX_lto.o/0.arm64.lto.o\";\n  std::string path1 = \"/Users/xxxx/Library/Developer/Xcode/DerivedData/xxxxxxxxxx/Build/Intermediates.noindex/xxxxxxxxxxx/arm64/XX_lto.o/1.arm64.lto.o\";\n  std::cout << path + \"\\n>>>            \" + path1 + \"\\n>>>            \" << std::endl;\n}\n\n// Issue #204\nstatic volatile void* global_p;\n\nstatic void t1main() {\n  mi_heap_t* heap = mi_heap_new();\n  global_p = mi_heap_malloc(heap, 1024);\n  mi_heap_delete(heap);\n}\n\nstatic void heap_late_free() {\n  auto t1 = std::thread(t1main);\n\n  msleep(2000);\n  assert(global_p);\n  mi_free((void*)global_p);\n\n  t1.join();\n}\n\n// issue  #209\nstatic void* shared_p;\nstatic void alloc0(/* void* arg */)\n{\n  shared_p = mi_malloc(8);\n}\n\nstatic void padding_shrink(void)\n{\n  auto t1 = std::thread(alloc0);\n  t1.join();\n  mi_free(shared_p);\n}\n\n\n// Issue #221\nstatic void heap_thread_free_large_worker() {\n  mi_free(shared_p);\n}\n\nstatic void heap_thread_free_large() {\n  for (int i = 0; i < 100; i++) {\n    shared_p = mi_malloc_aligned(2 * 1024 * 1024 + 1, 8);\n    auto t1 = std::thread(heap_thread_free_large_worker);\n    t1.join();\n  }\n}\n\nstatic void heap_thread_free_huge_worker() {\n  mi_free(shared_p);\n}\n\nstatic void heap_thread_free_huge() {\n  for (int i = 0; i < 100; i++) {\n    shared_p = mi_malloc(1024 * 1024 * 1024);\n    auto t1 = std::thread(heap_thread_free_huge_worker);\n    t1.join();\n  }\n}\n\n\nstatic std::atomic<long> xgsum;\n\nstatic void local_alloc() {\n  long sum = 0;\n  for (int i = 0; i < 1000000; i++) {\n    const int n = 1 + std::rand() % 1000;\n    uint8_t* p = (uint8_t*)calloc(n, 1);\n    p[0] = 1;\n    sum += p[std::rand() % n];\n    if ((std::rand() % 100) > 24) {\n      free(p);\n    }\n  }\n  xgsum += sum;\n}\n\nstatic void test_thread_leak(void) {\n  std::vector<std::thread> threads;\n  for (int i = 1; i<=100; ++i) {\n    threads.emplace_back(std::thread(&local_alloc));\n  }\n  for (auto& th : threads) {\n    th.join();\n  }\n}\n\n\nstatic void test_mt_shutdown()\n{\n  const int threads = 5;\n  std::vector< std::future< std::vector< char* > > > ts;\n\n  auto fn = [&]()\n  {\n    std::vector< char* > ps;\n    ps.reserve(1000);\n    for (int i = 0; i < 1000; i++)\n      ps.emplace_back(new char[1]);\n    return ps;\n  };\n\n  for (int i = 0; i < threads; i++)\n    ts.emplace_back(std::async(std::launch::async, fn));\n\n  for (auto& f : ts)\n    for (auto& p : f.get())\n      delete[] p;\n\n  std::cout << \"done\" << std::endl;\n}\n\n// issue #363\nusing namespace std;\n\nvoid large_alloc(void)\n{\n  char* a = new char[1ull << 25];\n  thread th([&] {\n    delete[] a;\n    });\n  th.join();\n}\n\n// issue #372\nstatic void fail_aslr() {\n  size_t sz = (size_t)(4ULL << 40); // 4TiB\n  void* p = malloc(sz);\n  printf(\"pointer p: %p: area up to %p\\n\", p, (uint8_t*)p + sz);\n  *(int*)0x5FFFFFFF000 = 0;  // should segfault\n}\n\n// issues #414\nstatic void dummy_worker() {\n  void* p = mi_malloc(0);\n  mi_free(p);\n}\n\nstatic void tsan_numa_test() {\n  auto t1 = std::thread(dummy_worker);\n  dummy_worker();\n  t1.join();\n}\n\n// issue #?\n#include <chrono>\n#include <random>\n#include <iostream>\n\nstatic void bench_alloc_large(void) {\n  static constexpr int kNumBuffers = 20;\n  static constexpr size_t kMinBufferSize = 5 * 1024 * 1024;\n  static constexpr size_t kMaxBufferSize = 25 * 1024 * 1024;\n  std::unique_ptr<char[]> buffers[kNumBuffers];\n\n  std::random_device rd;  (void)rd;\n  std::mt19937 gen(42); //rd());\n  std::uniform_int_distribution<> size_distribution(kMinBufferSize, kMaxBufferSize);\n  std::uniform_int_distribution<> buf_number_distribution(0, kNumBuffers - 1);\n\n  static constexpr int kNumIterations = 2000;\n  const auto start = std::chrono::steady_clock::now();\n  for (int i = 0; i < kNumIterations; ++i) {\n    int buffer_idx = buf_number_distribution(gen);\n    size_t new_size = size_distribution(gen);\n    buffers[buffer_idx] = std::make_unique<char[]>(new_size);\n  }\n  const auto end = std::chrono::steady_clock::now();\n  const auto num_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();\n  const auto us_per_allocation = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() / kNumIterations;\n  std::cout << kNumIterations << \" allocations Done in \" << num_ms << \"ms.\" << std::endl;\n  std::cout << \"Avg \" << us_per_allocation << \" us per allocation\" << std::endl;\n}\n\n\nclass MTest\n{\n    char *data;\npublic:\n    MTest() { data = (char*)malloc(1024); }\n    ~MTest() { free(data); };\n};\n\nthread_local MTest tlVariable;\n\nvoid threadFun( int i )\n{\n    printf( \"Thread %d\\n\", i );\n    std::this_thread::sleep_for( std::chrono::milliseconds(100) );\n}\n\nvoid test_thread_local()\n{\n    for( int i=1; i < 100; ++i )\n    {\n        std::thread t( threadFun, i );\n        t.join();\n        mi_stats_print(NULL);\n    }\n    return;\n}\n\nstatic std::atomic<long> gsum;\n\nconst int LEN[] = { 1000, 5000, 10000, 50000 };\n\n// adapted from example in\n// https://github.com/microsoft/mimalloc/issues/1104\n\nstatic void test_perf_local_alloc()\n{\n  // thread-local random number generator\n  std::minstd_rand rng(std::random_device{}());\n\n  long sum = 0;\n  for (int i = 0; i < 1000000; i++)\n  {\n    int len = LEN[rng() % 4];\n    int* p = (int*)mi_zalloc_aligned(len * sizeof(int), alignof(int));\n    p[0] = 1;\n    sum += p[rng() % len];\n    free(p);\n  }\n  std::cout << \".\";\n  gsum += sum;\n}\n\nstatic void test_perf_run()\n{\n  std::vector<std::thread> threads;\n  for (int i = 0; i < 24; ++i)\n  {\n    threads.emplace_back(std::thread(&test_perf_local_alloc));\n  }\n  for (auto& th : threads)\n  {\n    th.join();\n  }\n  std::cout << \"\\n\";\n}\n\nvoid test_perf(void)\n{\n  test_perf_run();\n  std::cout << \"gsum: \" << gsum.load() << \"\\n\";\n}\n\nstatic int sum2;\n\nstatic void escape(uint8_t* p, size_t n) { \n  if (n==0) return;\n  p[std::rand() % n] = 42;\n  sum2 += p[std::rand() % n];\n}\n\nvoid test_perf2(void) {  \n  for (size_t i = 0; i < 100000000; i++) {\n    const size_t n = 1000;\n    uint8_t* p = (uint8_t*)calloc(1, n);\n    escape(p,n);\n    free(p);\n  }\n}\n\nvoid test_perf3(void) {\n  for (size_t i = 0; i < 5; i++) {\n    const size_t n = (size_t)16*1024*1024*1024;\n    uint8_t* p = (uint8_t*)calloc(1, n);\n    escape(p, n);\n    free(p);\n  }\n}\n\n\nstatic void local_alloc4() {\n  for (int i = 0; i < 1000000; i++) {\n    const size_t n = i%1000;\n    uint8_t* p = (uint8_t*)calloc(1,n);\n    escape(p,n);\n    if (i % 4 > 0) {\n      free(p);\n    }\n  }\n}\n\nstatic void test_perf4(void) {\n  std::vector<std::thread> threads;\n  for (int i = 1; i <= 100; ++i) {\n    threads.emplace_back(std::thread(&local_alloc4));\n  }\n  for (auto& th : threads) {\n    th.join();\n  }\n}\n\n\nvoid escape5(uint8_t* p, size_t n) {\n  if (n==0) return;\n  for (size_t i = 0; i < n; i++) {\n    p[i] = (uint8_t)(i & 0xFF);\n  }\n  p[rand() % n] = (uint8_t)(n&0xFF);\n  // asm volatile(\"\" : : \"g\"(p) : \"memory\");   \n}\n\nstatic long gsum5;\n\nstatic void local_alloc5() {\n  long sum = 0;\n  for (int i = 0; i < 500000; i++) {\n    const size_t n = i % 1000;\n    uint8_t* p = (uint8_t*)mi_malloc(n);\n    escape5(p, n);\n    if (i % 4 > 0) {\n      if (n>0) { sum += p[n-1]; }\n      mi_free(p);\n    }\n  }\n  gsum5 += sum;\n}\n\nstatic void test_perf5(void) {\n  std::vector<std::thread> threads;\n  for (int i = 1; i <= 100; ++i) {\n    threads.emplace_back(std::thread(&local_alloc5));\n  }\n  for (auto& th : threads) {\n    th.join();\n  }\n  printf(\"gsum5: %li\\n\", gsum5);\n}\n"
  },
  {
    "path": "test/main.c",
    "content": "#include <stdio.h>\n#include <assert.h>\n#include <mimalloc.h>\n\nvoid test_heap(void* p_out) {\n  mi_heap_t* heap = mi_heap_new();\n  void* p1 = mi_heap_malloc(heap,32);\n  void* p2 = mi_heap_malloc(heap,48);\n  mi_free(p_out);\n  mi_heap_destroy(heap);\n  //mi_heap_delete(heap); mi_free(p1); mi_free(p2);\n}\n\nvoid test_large() {\n  const size_t N = 1000;\n\n  for (size_t i = 0; i < N; ++i) {\n    size_t sz = 1ull << 21;\n    char* a = mi_mallocn_tp(char,sz);\n    for (size_t k = 0; k < sz; k++) { a[k] = 'x'; }\n    mi_free(a);\n  }\n}\n\nint main() {\n  void* p1 = mi_malloc(16);\n  void* p2 = mi_malloc(1000000);\n  mi_free(p1);\n  mi_free(p2);\n  p1 = mi_malloc(16);\n  p2 = mi_malloc(16);\n  mi_free(p1);\n  mi_free(p2);\n\n  test_heap(mi_malloc(32));\n\n  p1 = mi_malloc_aligned(64, 16);\n  p2 = mi_malloc_aligned(160,24);\n  mi_free(p2);\n  mi_free(p1);\n  //test_large();\n\n  mi_collect(true);\n  mi_stats_print(NULL);\n  return 0;\n}\n"
  },
  {
    "path": "test/readme.md",
    "content": "Testing allocators is difficult as bugs may only surface after particular\nallocation patterns. The main approach to testing _mimalloc_ is therefore\nto have extensive internal invariant checking (see `page_is_valid` in `page.c`\nfor example), which is enabled in debug mode with `-DMI_DEBUG_FULL=ON`.\nThe main testing strategy is then to run [`mimalloc-bench`][bench] using full\ninvariant checking to catch any potential problems over a wide range of intensive\nallocation benchmarks and programs.\n\nHowever, this does not test well for the entire API surface and this is tested\nwith `test-api.c` when using `make test` (from `out/debug` etc). (This is\nnot complete yet, please add to it.)\n\nThe `main.c` and `main-override.c` are there to test if building and overriding\nfrom a local install works and therefore these build a separate `test/CMakeLists.txt`.\n\n[bench]: https://github.com/daanx/mimalloc-bench\n"
  },
  {
    "path": "test/test-api-fill.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#include \"mimalloc.h\"\n#include \"mimalloc/types.h\"\n\n#include \"testhelper.h\"\n\n// ---------------------------------------------------------------------------\n// Helper functions\n// ---------------------------------------------------------------------------\nbool check_zero_init(uint8_t* p, size_t size);\n#if MI_DEBUG >= 2\nbool check_debug_fill_uninit(uint8_t* p, size_t size);\nbool check_debug_fill_freed(uint8_t* p, size_t size);\n#endif\n\n// ---------------------------------------------------------------------------\n// Main testing\n// ---------------------------------------------------------------------------\nint main(void) {\n  mi_option_disable(mi_option_verbose);\n\n  // ---------------------------------------------------\n  // Zeroing allocation\n  // ---------------------------------------------------\n  CHECK_BODY(\"zeroinit-zalloc-small\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc(zalloc_size);\n    result = check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-zalloc-large\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_zalloc(zalloc_size);\n    result = check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-zalloc_small\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_small(zalloc_size);\n    result = check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-calloc-small\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_calloc(calloc_size, 1);\n    result = check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-calloc-large\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_calloc(calloc_size, 1);\n    result = check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-rezalloc-small\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc(zalloc_size);\n    result = check_zero_init(p, zalloc_size);\n    zalloc_size *= 3;\n    p = (uint8_t*)mi_rezalloc(p, zalloc_size);\n    result &= check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-rezalloc-large\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_zalloc(zalloc_size);\n    result = check_zero_init(p, zalloc_size);\n    zalloc_size *= 3;\n    p = (uint8_t*)mi_rezalloc(p, zalloc_size);\n    result &= check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-recalloc-small\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_calloc(calloc_size, 1);\n    result = check_zero_init(p, calloc_size);\n    calloc_size *= 3;\n    p = (uint8_t*)mi_recalloc(p, calloc_size, 1);\n    result &= check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-recalloc-large\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_calloc(calloc_size, 1);\n    result = check_zero_init(p, calloc_size);\n    calloc_size *= 3;\n    p = (uint8_t*)mi_recalloc(p, calloc_size, 1);\n    result &= check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n\n  // ---------------------------------------------------\n  // Zeroing in aligned API\n  // ---------------------------------------------------\n  CHECK_BODY(\"zeroinit-zalloc_aligned-small\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-zalloc_aligned-large\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-calloc_aligned-small\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_calloc_aligned(calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-calloc_aligned-large\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_calloc_aligned(calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-rezalloc_aligned-small\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, zalloc_size);\n    zalloc_size *= 3;\n    p = (uint8_t*)mi_rezalloc_aligned(p, zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-rezalloc_aligned-large\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, zalloc_size);\n    zalloc_size *= 3;\n    p = (uint8_t*)mi_rezalloc_aligned(p, zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_zero_init(p, zalloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"zeroinit-recalloc_aligned-small\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_calloc_aligned(calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, calloc_size);\n    calloc_size *= 3;\n    p = (uint8_t*)mi_recalloc_aligned(p, calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"zeroinit-recalloc_aligned-large\") {\n    size_t calloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_calloc_aligned(calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result = check_zero_init(p, calloc_size);\n    calloc_size *= 3;\n    p = (uint8_t*)mi_recalloc_aligned(p, calloc_size, 1, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_zero_init(p, calloc_size);\n    mi_free(p);\n  };\n\n#if (MI_DEBUG >= 2) && !MI_TSAN\n  // ---------------------------------------------------\n  // Debug filling\n  // ---------------------------------------------------\n  CHECK_BODY(\"uninit-malloc-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-malloc-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-malloc_small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc_small(malloc_size);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-realloc-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_realloc(p, malloc_size);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-realloc-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_realloc(p, malloc_size);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-mallocn-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_mallocn(malloc_size, 1);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-mallocn-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_mallocn(malloc_size, 1);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-reallocn-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_mallocn(malloc_size, 1);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_reallocn(p, malloc_size, 1);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-reallocn-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_mallocn(malloc_size, 1);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_reallocn(p, malloc_size, 1);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-malloc_aligned-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc_aligned(malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-malloc_aligned-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_malloc_aligned(malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"uninit-realloc_aligned-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc_aligned(malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_realloc_aligned(p, malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"uninit-realloc_aligned-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_malloc_aligned(malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = check_debug_fill_uninit(p, malloc_size);\n    malloc_size *= 3;\n    p = (uint8_t*)mi_realloc_aligned(p, malloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result &= check_debug_fill_uninit(p, malloc_size);\n    mi_free(p);\n  };\n\n  #if !(MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_GUARDED)\n  CHECK_BODY(\"fill-freed-small\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    mi_free(p);\n    // First sizeof(void*) bytes will contain housekeeping data, skip these\n    result = check_debug_fill_freed(p + sizeof(void*), malloc_size - sizeof(void*));\n  };\n  CHECK_BODY(\"fill-freed-large\") {\n    size_t malloc_size = MI_SMALL_SIZE_MAX * 2;\n    uint8_t* p = (uint8_t*)mi_malloc(malloc_size);\n    mi_free(p);\n    // First sizeof(void*) bytes will contain housekeeping data, skip these\n    result = check_debug_fill_freed(p + sizeof(void*), malloc_size - sizeof(void*));\n  };\n  #endif\n#endif\n\n  // ---------------------------------------------------\n  // Done\n  // ---------------------------------------------------[]\n  return print_test_summary();\n}\n\n// ---------------------------------------------------------------------------\n// Helper functions\n// ---------------------------------------------------------------------------\nbool check_zero_init(uint8_t* p, size_t size) {\n  if(!p)\n    return false;\n  bool result = true;\n  for (size_t i = 0; i < size; ++i) {\n    result &= p[i] == 0;\n  }\n  return result;\n}\n\n#if MI_DEBUG >= 2\nbool check_debug_fill_uninit(uint8_t* p, size_t size) {\n#if MI_TRACK_VALGRIND || MI_TRACK_ASAN\n  (void)p; (void)size;\n  return true; // when compiled with valgrind we don't init on purpose\n#else\n  if(!p)\n    return false;\n\n  bool result = true;\n  for (size_t i = 0; i < size; ++i) {\n    result &= p[i] == MI_DEBUG_UNINIT;\n  }\n  return result;\n#endif\n}\n\nbool check_debug_fill_freed(uint8_t* p, size_t size) {\n#if MI_TRACK_VALGRIND\n  (void)p; (void)size;\n  return true; // when compiled with valgrind we don't fill on purpose\n#else\n  if(!p)\n    return false;\n\n  bool result = true;\n  for (size_t i = 0; i < size; ++i) {\n    result &= p[i] == MI_DEBUG_FREED;\n  }\n  return result;\n#endif\n}\n#endif\n"
  },
  {
    "path": "test/test-api.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#if defined(__GNUC__) && !defined(__clang__)\n#pragma GCC diagnostic ignored \"-Walloc-size-larger-than=\"\n#endif\n\n/*\nTesting allocators is difficult as bugs may only surface after particular\nallocation patterns. The main approach to testing _mimalloc_ is therefore\nto have extensive internal invariant checking (see `page_is_valid` in `page.c`\nfor example), which is enabled in debug mode with `-DMI_DEBUG_FULL=ON`.\nThe main testing is then to run `mimalloc-bench` [1] using full invariant checking\nto catch any potential problems over a wide range of intensive allocation bench\nmarks.\n\nHowever, this does not test well for the entire API surface. In this test file\nwe therefore test the API over various inputs. Please add more tests :-)\n\n[1] https://github.com/daanx/mimalloc-bench\n*/\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <errno.h>\n\n#ifdef __cplusplus\n#include <vector>\n#endif\n\n#include \"mimalloc.h\"\n// #include \"mimalloc/internal.h\"\n#include \"mimalloc/types.h\" // for MI_DEBUG and MI_BLOCK_ALIGNMENT_MAX\n\n#include \"testhelper.h\"\n\n// ---------------------------------------------------------------------------\n// Test functions\n// ---------------------------------------------------------------------------\nbool test_heap1(void);\nbool test_heap2(void);\nbool test_stl_allocator1(void);\nbool test_stl_allocator2(void);\n\nbool test_stl_heap_allocator1(void);\nbool test_stl_heap_allocator2(void);\nbool test_stl_heap_allocator3(void);\nbool test_stl_heap_allocator4(void);\n\nbool mem_is_zero(uint8_t* p, size_t size) {\n  if (p==NULL) return false;\n  for (size_t i = 0; i < size; ++i) {\n    if (p[i] != 0) return false;\n  }\n  return true;\n}\n\n// ---------------------------------------------------------------------------\n// Main testing\n// ---------------------------------------------------------------------------\nint main(void) {\n  mi_option_disable(mi_option_verbose);\n\n  CHECK_BODY(\"malloc-aligned9a\") { // test large alignments\n    void* p = mi_zalloc_aligned(1024 * 1024, 2);\n    mi_free(p);\n    p = mi_zalloc_aligned(1024 * 1024, 2);\n    mi_free(p);\n    result = true;\n  };\n  \n\n  // ---------------------------------------------------\n  // Malloc\n  // ---------------------------------------------------\n\n  CHECK_BODY(\"malloc-zero\") {\n    void* p = mi_malloc(0);\n    result = (p != NULL);\n    mi_free(p);\n  };\n  CHECK_BODY(\"malloc-nomem1\") {\n    result = (mi_malloc((size_t)PTRDIFF_MAX + (size_t)1) == NULL);\n  };\n  CHECK_BODY(\"malloc-null\") {\n    mi_free(NULL);\n  };\n  CHECK_BODY(\"calloc-overflow\") {\n    // use (size_t)&mi_calloc to get some number without triggering compiler warnings\n    result = (mi_calloc((size_t)&mi_calloc,SIZE_MAX/1000) == NULL);\n  };\n  CHECK_BODY(\"calloc0\") {\n    void* p = mi_calloc(0,1000);\n    result = (mi_usable_size(p) <= 16);\n    mi_free(p);\n  };\n  CHECK_BODY(\"malloc-large\") {   // see PR #544.\n    void* p = mi_malloc(67108872);\n    mi_free(p);\n  };\n\n  // ---------------------------------------------------\n  // Extended\n  // ---------------------------------------------------\n  CHECK_BODY(\"posix_memalign1\") {\n    void* p = &p;\n    int err = mi_posix_memalign(&p, sizeof(void*), 32);\n    result = ((err==0 && (uintptr_t)p % sizeof(void*) == 0) || p==&p);\n    mi_free(p);\n  };\n  CHECK_BODY(\"posix_memalign_no_align\") {\n    void* p = &p;\n    int err = mi_posix_memalign(&p, 3, 32);\n    result = (err==EINVAL && p==&p);\n  };\n  CHECK_BODY(\"posix_memalign_zero\") {\n    void* p = &p;\n    int err = mi_posix_memalign(&p, sizeof(void*), 0);\n    mi_free(p);\n    result = (err==0);\n  };\n  CHECK_BODY(\"posix_memalign_nopow2\") {\n    void* p = &p;\n    int err = mi_posix_memalign(&p, 3*sizeof(void*), 32);\n    result = (err==EINVAL && p==&p);\n  };\n  CHECK_BODY(\"posix_memalign_nomem\") {\n    void* p = &p;\n    int err = mi_posix_memalign(&p, sizeof(void*), SIZE_MAX);\n    result = (err==ENOMEM && p==&p);\n  };\n\n  // ---------------------------------------------------\n  // Aligned API\n  // ---------------------------------------------------\n  CHECK_BODY(\"malloc-aligned1\") {\n    void* p = mi_malloc_aligned(32,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p);\n  };\n  CHECK_BODY(\"malloc-aligned2\") {\n    void* p = mi_malloc_aligned(48,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p);\n  };\n  CHECK_BODY(\"malloc-aligned3\") {\n    void* p1 = mi_malloc_aligned(48,32); bool result1 = (p1 != NULL && (uintptr_t)(p1) % 32 == 0);\n    void* p2 = mi_malloc_aligned(48,32); bool result2 = (p2 != NULL && (uintptr_t)(p2) % 32 == 0);\n    mi_free(p2);\n    mi_free(p1);\n    result = (result1&&result2);\n  };\n  CHECK_BODY(\"malloc-aligned4\") {\n    void* p;\n    bool ok = true;\n    for (int i = 0; i < 8 && ok; i++) {\n      p = mi_malloc_aligned(8, 16);\n      ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p);\n    }\n    result = ok;\n  };\n  CHECK_BODY(\"malloc-aligned5\") {\n    void* p = mi_malloc_aligned(4097,4096);\n    size_t usable = mi_usable_size(p);\n    result = (usable >= 4097 && usable < 16000);\n    printf(\"malloc_aligned5: usable size: %zi\\n\", usable);\n    mi_free(p);\n  };\n  /*\n  CHECK_BODY(\"malloc-aligned6\") {\n    bool ok = true;\n    for (size_t align = 1; align <= MI_BLOCK_ALIGNMENT_MAX && ok; align *= 2) {\n      void* ps[8];\n      for (int i = 0; i < 8 && ok; i++) {\n        ps[i] = mi_malloc_aligned(align*13  // size\n                                 , align);\n        if (ps[i] == NULL || (uintptr_t)(ps[i]) % align != 0) {\n          ok = false;\n        }\n      }\n      for (int i = 0; i < 8 && ok; i++) {\n        mi_free(ps[i]);\n      }\n    }\n    result = ok;\n  };\n  */\n  CHECK_BODY(\"malloc-aligned7\") {\n    void* p = mi_malloc_aligned(1024,MI_BLOCK_ALIGNMENT_MAX);\n    mi_free(p);\n    result = ((uintptr_t)p % MI_BLOCK_ALIGNMENT_MAX) == 0;\n  };\n  CHECK_BODY(\"malloc-aligned8\") {\n    bool ok = true;\n    for (int i = 0; i < 5 && ok; i++) {\n      int n = (1 << i);\n      void* p = mi_malloc_aligned(1024, n * MI_BLOCK_ALIGNMENT_MAX);\n      ok = ((uintptr_t)p % (n*MI_BLOCK_ALIGNMENT_MAX)) == 0;\n      mi_free(p);\n    }\n    result = ok;\n  };\n  CHECK_BODY(\"malloc-aligned9\") { // test large alignments\n    bool ok = true;\n    void* p[8];\n    size_t sizes[8] = { 8, 512, 1024 * 1024, MI_BLOCK_ALIGNMENT_MAX, MI_BLOCK_ALIGNMENT_MAX + 1, \n      #if SIZE_MAX > UINT32_MAX\n      2 * MI_BLOCK_ALIGNMENT_MAX, 8 * MI_BLOCK_ALIGNMENT_MAX, \n      #endif\n      0 };\n    for (int i = 0; i < 28 && ok; i++) {\n      int align = (1 << i);\n      for (int j = 0; j < 8 && ok; j++) {\n        p[j] = mi_zalloc_aligned(sizes[j], align);\n        ok = ((uintptr_t)p[j] % align) == 0;\n      }\n      for (int j = 0; j < 8; j++) {\n        mi_free(p[j]);\n      }\n    }\n    result = ok;\n  };\n  CHECK_BODY(\"malloc-aligned10\") {\n    bool ok = true;\n    void* p[10+1];\n    int align;\n    int j;\n    for(j = 0, align = 1; j <= 10 && ok; align *= 2, j++ ) {\n      p[j] = mi_malloc_aligned(43 + align, align);\n      ok = ((uintptr_t)p[j] % align) == 0;\n    }\n    for ( ; j > 0; j--) {\n      mi_free(p[j-1]);\n    }\n    result = ok;\n  }\n  CHECK_BODY(\"malloc_aligned11\") {\n    mi_heap_t* heap = mi_heap_new();\n    void* p = mi_heap_malloc_aligned(heap, 33554426, 8);\n    result = mi_heap_contains_block(heap, p);\n    mi_heap_destroy(heap);\n  }\n  CHECK_BODY(\"mimalloc-aligned12\") {\n    void* p = mi_malloc_aligned(0x100, 0x100);\n    result = (((uintptr_t)p % 0x100) == 0); // #602\n    mi_free(p);\n  }\n  CHECK_BODY(\"mimalloc-aligned13\") {\n    bool ok = true;\n    for( size_t size = 1; size <= (MI_SMALL_SIZE_MAX * 2) && ok; size++ ) {\n      for(size_t align = 1; align <= size && ok; align *= 2 ) {\n        void* p[10];\n        for(int i = 0; i < 10 && ok; i++) {\n          p[i] = mi_malloc_aligned(size,align);;\n          ok = (p[i] != NULL && ((uintptr_t)(p[i]) % align) == 0);\n        }\n        for(int i = 0; i < 10 && ok; i++) {\n          mi_free(p[i]);\n        }       \n        /*\n        if (ok && align <= size && ((size + MI_PADDING_SIZE) & (align-1)) == 0) {\n          size_t bsize = mi_good_size(size);\n          ok = (align <= bsize && (bsize & (align-1)) == 0);\n        }\n        */\n      }\n    }\n    result = ok;\n  }\n  CHECK_BODY(\"malloc-aligned-at1\") {\n    void* p = mi_malloc_aligned_at(48,32,0); result = (p != NULL && ((uintptr_t)(p) + 0) % 32 == 0); mi_free(p);\n  };\n  CHECK_BODY(\"malloc-aligned-at2\") {\n    void* p = mi_malloc_aligned_at(50,32,8); result = (p != NULL && ((uintptr_t)(p) + 8) % 32 == 0); mi_free(p);\n  };\n  CHECK_BODY(\"memalign1\") {\n    void* p;\n    bool ok = true;\n    for (int i = 0; i < 8 && ok; i++) {\n      p = mi_memalign(16,8);\n      ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p);\n    }\n    result = ok;\n  };\n  CHECK_BODY(\"zalloc-aligned-small1\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = mem_is_zero(p, zalloc_size);\n    mi_free(p);\n  };\n  CHECK_BODY(\"rezalloc_aligned-small1\") {\n    size_t zalloc_size = MI_SMALL_SIZE_MAX / 2;\n    uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = mem_is_zero(p, zalloc_size);\n    zalloc_size *= 3;\n    p = (uint8_t*)mi_rezalloc_aligned(p, zalloc_size, MI_MAX_ALIGN_SIZE * 2);\n    result = result && mem_is_zero(p, zalloc_size);\n    mi_free(p);\n  };\n\n  // ---------------------------------------------------\n  // Reallocation\n  // ---------------------------------------------------\n  CHECK_BODY(\"realloc-null\") {\n    void* p = mi_realloc(NULL,4);\n    result = (p != NULL);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"realloc-null-sizezero\") {\n    void* p = mi_realloc(NULL,0);  // <https://en.cppreference.com/w/c/memory/realloc> \"If ptr is NULL, the behavior is the same as calling malloc(new_size).\"\n    result = (p != NULL);\n    mi_free(p);\n  };\n\n  CHECK_BODY(\"realloc-sizezero\") {\n    void* p = mi_malloc(4);\n    void* q = mi_realloc(p, 0);\n    result = (q != NULL);\n    mi_free(q);\n  };\n\n  CHECK_BODY(\"reallocarray-null-sizezero\") {\n    void* p = mi_reallocarray(NULL,0,16);  // issue #574\n    result = (p != NULL && errno == 0);\n    mi_free(p);\n  };\n\n  // ---------------------------------------------------\n  // Returned block sizes\n  // ---------------------------------------------------\n  CHECK_BODY(\"umalloc1\") {\n    for(size_t size = 1; size <= 32*MI_MiB; size *= 2 ) {\n      size_t bsize;\n      void* p = mi_umalloc(size,&bsize);\n      assert(bsize >= size);\n      size_t pre_size;\n      size_t post_size;\n      p = mi_urealloc(p, size + 1024, &pre_size, &post_size);\n      assert(pre_size == bsize);\n      assert(post_size >= size + 1024);\n      size_t fsize;\n      mi_ufree(p,&fsize);\n      assert(fsize == post_size);\n    }\n  }\n\n  // ---------------------------------------------------\n  // Heaps\n  // ---------------------------------------------------\n  CHECK(\"heap_destroy\", test_heap1());\n  CHECK(\"heap_delete\", test_heap2());\n\n  //mi_stats_print(NULL);\n\n  // ---------------------------------------------------\n  // various\n  // ---------------------------------------------------\n  #if !defined(MI_TRACK_ASAN)   // realpath may leak with ASAN enabled (as the ASAN allocator intercepts it)\n  CHECK_BODY(\"realpath\") {\n    char* s = mi_realpath( \".\", NULL );\n    // printf(\"realpath: %s\\n\",s);\n    mi_free(s);\n  };\n  #endif\n\n  CHECK(\"stl_allocator1\", test_stl_allocator1());\n  CHECK(\"stl_allocator2\", test_stl_allocator2());\n\n\tCHECK(\"stl_heap_allocator1\", test_stl_heap_allocator1());\n\tCHECK(\"stl_heap_allocator2\", test_stl_heap_allocator2());\n\tCHECK(\"stl_heap_allocator3\", test_stl_heap_allocator3());\n\tCHECK(\"stl_heap_allocator4\", test_stl_heap_allocator4());\n\n  // ---------------------------------------------------\n  // Done\n  // ---------------------------------------------------[]\n  return print_test_summary();\n}\n\n// ---------------------------------------------------\n// Larger test functions\n// ---------------------------------------------------\n\nbool test_heap1(void) {\n  mi_heap_t* heap = mi_heap_new();\n  int* p1 = mi_heap_malloc_tp(heap,int);\n  int* p2 = mi_heap_malloc_tp(heap,int);\n  *p1 = *p2 = 43;\n  mi_heap_destroy(heap);\n  return true;\n}\n\nbool test_heap2(void) {\n  mi_heap_t* heap = mi_heap_new();\n  int* p1 = mi_heap_malloc_tp(heap,int);\n  int* p2 = mi_heap_malloc_tp(heap,int);\n  mi_heap_delete(heap);\n  *p1 = 42;\n  mi_free(p1);\n  mi_free(p2);\n  return true;\n}\n\nbool test_stl_allocator1(void) {\n#ifdef __cplusplus\n  std::vector<int, mi_stl_allocator<int> > vec;\n  vec.push_back(1);\n  vec.pop_back();\n  return vec.size() == 0;\n#else\n  return true;\n#endif\n}\n\nstruct some_struct  { int i; int j; double z; };\n\nbool test_stl_allocator2(void) {\n#ifdef __cplusplus\n  std::vector<some_struct, mi_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n#else\n  return true;\n#endif\n}\n\nbool test_stl_heap_allocator1(void) {\n#ifdef __cplusplus\n  std::vector<some_struct, mi_heap_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n#else\n  return true;\n#endif\n}\n\nbool test_stl_heap_allocator2(void) {\n#ifdef __cplusplus\n  std::vector<some_struct, mi_heap_destroy_stl_allocator<some_struct> > vec;\n  vec.push_back(some_struct());\n  vec.pop_back();\n  return vec.size() == 0;\n#else\n  return true;\n#endif\n}\n\nbool test_stl_heap_allocator3(void) {\n#ifdef __cplusplus\n\tmi_heap_t* heap = mi_heap_new();\n\tbool good = false;\n\t{\n\t\tmi_heap_stl_allocator<some_struct> myAlloc(heap);\n\t\tstd::vector<some_struct, mi_heap_stl_allocator<some_struct> > vec(myAlloc);\n\t\tvec.push_back(some_struct());\n\t\tvec.pop_back();\n\t\tgood = vec.size() == 0;\n\t}\n\tmi_heap_delete(heap);\n  return good;\n#else\n  return true;\n#endif\n}\n\nbool test_stl_heap_allocator4(void) {\n#ifdef __cplusplus\n\tmi_heap_t* heap = mi_heap_new();\n\tbool good = false;\n\t{\n\t\tmi_heap_destroy_stl_allocator<some_struct> myAlloc(heap);\n\t\tstd::vector<some_struct, mi_heap_destroy_stl_allocator<some_struct> > vec(myAlloc);\n\t\tvec.push_back(some_struct());\n\t\tvec.pop_back();\n\t\tgood = vec.size() == 0;\n\t}\n\tmi_heap_destroy(heap);\n  return good;\n#else\n  return true;\n#endif\n}\n"
  },
  {
    "path": "test/test-stress.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020 Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license.\n-----------------------------------------------------------------------------*/\n\n/* This is a stress test for the allocator, using multiple threads and\n   transferring objects between threads. It tries to reflect real-world workloads:\n   - allocation size is distributed linearly in powers of two\n   - with some fraction extra large (and some very large)\n   - the allocations are initialized and read again at free\n   - pointers transfer between threads\n   - threads are terminated and recreated with some objects surviving in between\n   - uses deterministic \"randomness\", but execution can still depend on\n     (random) thread scheduling. Do not use this test as a benchmark!\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <string.h>\n#include <assert.h>\n\n// #define MI_GUARDED\n// #define USE_STD_MALLOC\n\n// > mimalloc-test-stress [THREADS] [SCALE] [ITER]\n//\n// argument defaults\n#if defined(MI_TSAN)          // with thread-sanitizer reduce the threads to test within the azure pipeline limits\nstatic int THREADS = 8;\nstatic int SCALE   = 25;\nstatic int ITER    = 400;\n#elif defined(MI_UBSAN)       // with undefined behavious sanitizer reduce parameters to stay within the azure pipeline limits\nstatic int THREADS = 8;\nstatic int SCALE   = 25;\nstatic int ITER    = 20;\n#elif defined(MI_GUARDED)     // with debug guard pages reduce parameters to stay within the azure pipeline limits\nstatic int THREADS = 8;\nstatic int SCALE   = 10;\nstatic int ITER    = 10;\n#else\nstatic int THREADS = 32;      // more repeatable if THREADS <= #processors\nstatic int SCALE   = 50;      // scaling factor\nstatic int ITER    = 50;      // N full iterations destructing and re-creating all threads\n#endif\n\n\n\n#define STRESS                // undefine for leak test\n\nstatic bool   allow_large_objects = false;     // allow very large objects? (set to `true` if SCALE>100)\nstatic size_t use_one_size = 0;               // use single object size of `N * sizeof(uintptr_t)`?\n\nstatic bool   main_participates = false;       // main thread participates as a worker too\n\n#ifdef USE_STD_MALLOC\n#define custom_calloc(n,s)    calloc(n,s)\n#define custom_realloc(p,s)   realloc(p,s)\n#define custom_free(p)        free(p)\n#else\n#include <mimalloc.h>\n#include <mimalloc-stats.h>\n#define custom_calloc(n,s)    mi_calloc(n,s)\n#define custom_realloc(p,s)   mi_realloc(p,s)\n#define custom_free(p)        mi_free(p)\n\n#ifndef NDEBUG\n#define HEAP_WALK             // walk the heap objects?\n#endif\n#endif\n\n// transfer pointer between threads\n#define TRANSFERS     (1000)\nstatic volatile void* transfer[TRANSFERS];\n\n\n#if (UINTPTR_MAX != UINT32_MAX)\nconst uintptr_t cookie = 0xbf58476d1ce4e5b9UL;\n#else\nconst uintptr_t cookie = 0x1ce4e5b9UL;\n#endif\n\nstatic void* atomic_exchange_ptr(volatile void** p, void* newval);\n\ntypedef uintptr_t* random_t;\n\nstatic uintptr_t pick(random_t r) {\n  uintptr_t x = *r;\n#if (UINTPTR_MAX > UINT32_MAX)\n  // by Sebastiano Vigna, see: <http://xoshiro.di.unimi.it/splitmix64.c>\n  x ^= x >> 30;\n  x *= 0xbf58476d1ce4e5b9UL;\n  x ^= x >> 27;\n  x *= 0x94d049bb133111ebUL;\n  x ^= x >> 31;\n#else\n  // by Chris Wellons, see: <https://nullprogram.com/blog/2018/07/31/>\n  x ^= x >> 16;\n  x *= 0x7feb352dUL;\n  x ^= x >> 15;\n  x *= 0x846ca68bUL;\n  x ^= x >> 16;\n#endif\n  *r = x;\n  return x;\n}\n\nstatic bool chance(size_t perc, random_t r) {\n  return (pick(r) % 100 <= perc);\n}\n\nstatic void* alloc_items(size_t items, random_t r) {\n  if (chance(1, r)) {\n    if (chance(1, r) && allow_large_objects) items *= 10000;       // 0.01% giant\n    else if (chance(10, r) && allow_large_objects) items *= 1000;  // 0.1% huge\n    else items *= 100;                                             // 1% large objects;\n  }\n  if (items>=32 && items<=40) items*=2;              // pthreads uses 320b allocations (this shows that more clearly in the stats)\n  if (use_one_size > 0) items = (use_one_size / sizeof(uintptr_t));\n  if (items==0) items = 1;\n  uintptr_t* p = (uintptr_t*)custom_calloc(items,sizeof(uintptr_t));\n  if (p != NULL) {\n    for (uintptr_t i = 0; i < items; i++) {\n      assert(p[i] == 0);\n      p[i] = (items - i) ^ cookie;\n    }\n  }\n  return p;\n}\n\nstatic void free_items(void* p) {\n  if (p != NULL) {\n    uintptr_t* q = (uintptr_t*)p;\n    uintptr_t items = (q[0] ^ cookie);\n    for (uintptr_t i = 0; i < items; i++) {\n      if ((q[i] ^ cookie) != items - i) {\n        fprintf(stderr, \"memory corruption at block %p at %zu\\n\", p, i);\n        abort();\n      }\n    }\n  }\n  custom_free(p);\n}\n\n#ifdef HEAP_WALK\nstatic bool visit_blocks(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg) {\n  (void)(heap); (void)(area);\n  size_t* total = (size_t*)arg;\n  if (block != NULL) {\n    *total += block_size;\n  }\n  return true;\n}\n#endif\n\nstatic void stress(intptr_t tid) {\n  //bench_start_thread();\n  uintptr_t r = ((tid + 1) * 43); // rand();\n  const size_t max_item_shift = 5; // 128\n  const size_t max_item_retained_shift = max_item_shift + 2;\n  size_t allocs = 100 * ((size_t)SCALE) * (tid % 8 + 1); // some threads do more\n  size_t retain = allocs / 2;\n  void** data = NULL;\n  size_t data_size = 0;\n  size_t data_top = 0;\n  void** retained = (void**)custom_calloc(retain,sizeof(void*));\n  size_t retain_top = 0;\n\n  while (allocs > 0 || retain > 0) {\n    if (retain == 0 || (chance(50, &r) && allocs > 0)) {\n      // 50%+ alloc\n      allocs--;\n      if (data_top >= data_size) {\n        data_size += 100000;\n        data = (void**)custom_realloc(data, data_size * sizeof(void*));\n      }\n      data[data_top++] = alloc_items(1ULL << (pick(&r) % max_item_shift), &r);\n    }\n    else {\n      // 25% retain\n      retained[retain_top++] = alloc_items( 1ULL << (pick(&r) % max_item_retained_shift), &r);\n      retain--;\n    }\n    if (chance(66, &r) && data_top > 0) {\n      // 66% free previous alloc\n      size_t idx = pick(&r) % data_top;\n      free_items(data[idx]);\n      data[idx] = NULL;\n    }\n    if (chance(25, &r) && data_top > 0) {\n      // 25% exchange a local pointer with the (shared) transfer buffer.\n      size_t data_idx = pick(&r) % data_top;\n      size_t transfer_idx = pick(&r) % TRANSFERS;\n      void* p = data[data_idx];\n      void* q = atomic_exchange_ptr(&transfer[transfer_idx], p);\n      data[data_idx] = q;\n    }\n  }\n\n  #ifdef HEAP_WALK\n  // walk the heap\n  size_t total = 0;\n  mi_heap_visit_blocks(mi_heap_get_default(), true, visit_blocks, &total);\n  #endif\n\n  // free everything that is left\n  for (size_t i = 0; i < retain_top; i++) {\n    free_items(retained[i]);\n  }\n  for (size_t i = 0; i < data_top; i++) {\n    free_items(data[i]);\n  }\n  custom_free(retained);\n  custom_free(data);\n  //bench_end_thread();\n}\n\nstatic void run_os_threads(size_t nthreads, void (*entry)(intptr_t tid));\n\nstatic void test_stress(void) {\n  uintptr_t r = rand();\n  for (int n = 0; n < ITER; n++) {\n    run_os_threads(THREADS, &stress);\n    #if !defined(NDEBUG) && !defined(USE_STD_MALLOC)\n    // switch between arena and OS allocation for testing\n    // mi_option_set_enabled(mi_option_disallow_arena_alloc, (n%2)==1);\n    #endif\n    #ifdef HEAP_WALK\n    size_t total = 0;\n    mi_abandoned_visit_blocks(mi_subproc_main(), -1, true, visit_blocks, &total);\n    #endif\n    for (int i = 0; i < TRANSFERS; i++) {\n      if (chance(50, &r) || n + 1 == ITER) { // free all on last run, otherwise free half of the transfers\n        void* p = atomic_exchange_ptr(&transfer[i], NULL);\n        free_items(p);\n      }\n    }\n    #ifndef NDEBUG\n    //mi_collect(false);\n    //mi_debug_show_arenas(true);\n    #endif\n    #if !defined(NDEBUG) || defined(MI_TSAN)\n    if ((n + 1) % 10 == 0) { printf(\"- iterations left: %3d\\n\", ITER - (n + 1)); }\n    #endif\n  }\n}\n\n#ifndef STRESS\nstatic void leak(intptr_t tid) {\n  uintptr_t r = rand();\n  void* p = alloc_items(1 /*pick(&r)%128*/, &r);\n  if (chance(50, &r)) {\n    intptr_t i = (pick(&r) % TRANSFERS);\n    void* q = atomic_exchange_ptr(&transfer[i], p);\n    free_items(q);\n  }\n}\n\nstatic void test_leak(void) {\n  for (int n = 0; n < ITER; n++) {\n    run_os_threads(THREADS, &leak);\n    mi_collect(false);\n#ifndef NDEBUG\n    if ((n + 1) % 10 == 0) { printf(\"- iterations left: %3d\\n\", ITER - (n + 1)); }\n#endif\n  }\n}\n#endif\n\n#if defined(USE_STD_MALLOC) && defined(MI_LINK_VERSION)\n#ifdef __cplusplus\nextern \"C\"\n#endif\nint mi_version(void);\n#endif\n\nint main(int argc, char** argv) {\n  #ifdef MI_LINK_VERSION\n    mi_version();\n  #endif\n  #ifdef HEAP_WALK\n    mi_option_enable(mi_option_visit_abandoned);\n  #endif\n  #if !defined(NDEBUG) && !defined(USE_STD_MALLOC)\n    mi_option_set(mi_option_arena_reserve, 32 * 1024 /* in kib = 32MiB */);\n  #endif\n\n  // > mimalloc-test-stress [THREADS] [SCALE] [ITER]\n  if (argc >= 2) {\n    char* end;\n    long n = strtol(argv[1], &end, 10);\n    if (n > 0) THREADS = n;\n  }\n  if (argc >= 3) {\n    char* end;\n    long n = (strtol(argv[2], &end, 10));\n    if (n > 0) SCALE = n;\n  }\n  if (argc >= 4) {\n    char* end;\n    long n = (strtol(argv[3], &end, 10));\n    if (n > 0) ITER = n;\n  }\n  if (SCALE > 100) {\n    allow_large_objects = true;\n  }\n  printf(\"Using %d threads with a %d%% load-per-thread and %d iterations %s\\n\", THREADS, SCALE, ITER, (allow_large_objects ? \"(allow large objects)\" : \"\"));\n\n  #if !defined(NDEBUG) && !defined(USE_STD_MALLOC)\n  mi_stats_reset();\n  #endif\n\n  //mi_reserve_os_memory(1024*1024*1024ULL, false, true);\n  //int res = mi_reserve_huge_os_pages(4,1);\n  //printf(\"(reserve huge: %i\\n)\", res);\n\n  //bench_start_program();\n\n  // Run ITER full iterations where half the objects in the transfer buffer survive to the next round.\n  srand(0x7feb352d);\n  \n  //mi_reserve_os_memory(512ULL << 20, true, true);\n\n  #if !defined(NDEBUG) && !defined(USE_STD_MALLOC)\n  mi_stats_reset();\n  #endif\n\n#ifdef STRESS\n  test_stress();\n#else\n  test_leak();\n#endif\n\n#ifndef USE_STD_MALLOC\n  #ifndef NDEBUG\n  mi_debug_show_arenas();\n  mi_collect(true);\n  char* json = mi_stats_get_json(0, NULL);\n  if (json != NULL) {\n    fputs(json,stderr);\n    mi_free(json);\n  }\n  #endif\n  mi_collect(true);\n  mi_stats_print(NULL);  \n#endif\n  //bench_end_program();\n  return 0;\n}\n\n\nstatic void (*thread_entry_fun)(intptr_t) = &stress;\n\n#ifdef _WIN32\n\n#include <windows.h>\n\nstatic DWORD WINAPI thread_entry(LPVOID param) {\n  thread_entry_fun((intptr_t)param);\n  return 0;\n}\n\nstatic void run_os_threads(size_t nthreads, void (*fun)(intptr_t)) {\n  thread_entry_fun = fun;\n  DWORD* tids = (DWORD*)custom_calloc(nthreads,sizeof(DWORD));\n  HANDLE* thandles = (HANDLE*)custom_calloc(nthreads,sizeof(HANDLE));\n  const size_t start = (main_participates ? 1 : 0);\n  for (size_t i = start; i < nthreads; i++) {\n    thandles[i] = CreateThread(0, 8*1024, &thread_entry, (void*)(i), 0, &tids[i]);\n  }\n  if (main_participates) fun(0); // run the main thread as well\n  for (size_t i = start; i < nthreads; i++) {\n    WaitForSingleObject(thandles[i], INFINITE);\n  }\n  for (size_t i = start; i < nthreads; i++) {\n    CloseHandle(thandles[i]);\n  }\n  custom_free(tids);\n  custom_free(thandles);\n}\n\nstatic void* atomic_exchange_ptr(volatile void** p, void* newval) {\n#if (INTPTR_MAX == INT32_MAX)\n  return (void*)InterlockedExchange((volatile LONG*)p, (LONG)newval);\n#else\n  return (void*)InterlockedExchange64((volatile LONG64*)p, (LONG64)newval);\n#endif\n}\n#else\n\n#include <pthread.h>\n\nstatic void* thread_entry(void* param) {\n  thread_entry_fun((uintptr_t)param);\n  return NULL;\n}\n\nstatic void run_os_threads(size_t nthreads, void (*fun)(intptr_t)) {\n  thread_entry_fun = fun;\n  pthread_t* threads = (pthread_t*)custom_calloc(nthreads,sizeof(pthread_t));\n  memset(threads, 0, sizeof(pthread_t) * nthreads);\n  const size_t start = (main_participates ? 1 : 0);\n  //pthread_setconcurrency(nthreads);\n  for (size_t i = start; i < nthreads; i++) {\n    pthread_create(&threads[i], NULL, &thread_entry, (void*)i);\n  }\n  if (main_participates) fun(0); // run the main thread as well\n  for (size_t i = start; i < nthreads; i++) {\n    pthread_join(threads[i], NULL);\n  }\n  custom_free(threads);\n}\n\n#ifdef __cplusplus\n#include <atomic>\nstatic void* atomic_exchange_ptr(volatile void** p, void* newval) {\n  return std::atomic_exchange((volatile std::atomic<void*>*)p, newval);\n}\n#else\n#include <stdatomic.h>\nstatic void* atomic_exchange_ptr(volatile void** p, void* newval) {\n  return atomic_exchange((volatile _Atomic(void*)*)p, newval);\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "test/test-wrong.c",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n\n/* test file for valgrind/asan support.\n\n   VALGRIND:\n   ----------\n   Compile in an \"out/debug\" folder:\n\n   > cd out/debug\n   > cmake ../.. -DMI_TRACK_VALGRIND=1\n   > make -j8\n\n   and then compile this file as:\n\n   > gcc -g -o test-wrong -I../../include ../../test/test-wrong.c libmimalloc-valgrind-debug.a -lpthread\n\n   and test as:\n\n   > valgrind ./test-wrong\n\n   \n   ASAN\n   ----------\n   Compile in an \"out/debug\" folder:\n\n   > cd out/debug\n   > cmake ../.. -DMI_TRACK_ASAN=1\n   > make -j8\n\n   and then compile this file as:\n\n   > clang -g -o test-wrong -I../../include ../../test/test-wrong.c libmimalloc-asan-debug.a -lpthread -fsanitize=address -fsanitize-recover=address\n\n   and test as:\n\n   > ASAN_OPTIONS=verbosity=1:halt_on_error=0 ./test-wrong\n\n\n*/\n#include <stdio.h>\n#include <stdlib.h>\n#include \"mimalloc.h\"\n\n#ifdef USE_STD_MALLOC\n# define mi(x) x\n#else\n# define mi(x) mi_##x\n#endif\n\nint main(int argc, char** argv) {\n  int* p = (int*)mi(malloc)(3*sizeof(int));\n\n  int* r = (int*)mi_malloc_aligned(8,16);\n  mi_free(r);\n\n  // illegal byte wise read\n  char* c = (char*)mi(malloc)(3);\n  printf(\"invalid byte: over: %d, under: %d\\n\", c[4], c[-1]);\n  mi(free)(c);\n\n  // undefined access\n  int* q = (int*)mi(malloc)(sizeof(int));\n  printf(\"undefined: %d\\n\", *q);\n\n  // illegal int read\n  printf(\"invalid: over: %d, under: %d\\n\", q[1], q[-1]);\n\n  *q = 42;\n\n  // buffer overflow\n  q[1] = 43;\n\n  // buffer underflow\n  q[-1] = 44;\n\n  mi(free)(q);\n\n  // double free\n  mi(free)(q);\n\n  // use after free\n  printf(\"use-after-free: %d\\n\", *q);\n\n  // leak p\n  // mi_free(p)\n  return 0;\n}"
  },
  {
    "path": "test/testhelper.h",
    "content": "/* ----------------------------------------------------------------------------\nCopyright (c) 2018-2020, Microsoft Research, Daan Leijen\nThis is free software; you can redistribute it and/or modify it under the\nterms of the MIT license. A copy of the license can be found in the file\n\"LICENSE\" at the root of this distribution.\n-----------------------------------------------------------------------------*/\n#ifndef TESTHELPER_H_\n#define TESTHELPER_H_\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <errno.h>\n\n// ---------------------------------------------------------------------------\n// Test macros: CHECK(name,predicate) and CHECK_BODY(name,body)\n// ---------------------------------------------------------------------------\nstatic int ok = 0;\nstatic int failed = 0;\n\nstatic bool check_result(bool result, const char* testname, const char* fname, long lineno) {\n  if (!(result)) {\n    failed++;\n    fprintf(stderr,\"\\n  FAILED: %s: %s:%ld\\n\", testname, fname, lineno);\n    /* exit(1); */\n  }\n  else {\n    ok++;\n    fprintf(stderr, \"ok.\\n\");\n  }\n  return true;\n}\n\n#define CHECK_BODY(name) \\\n  fprintf(stderr,\"test: %s...  \", name ); \\\n  errno = 0; \\\n  for(bool done = false, result = true; !done; done = check_result(result,name,__FILE__,__LINE__))\n\n#define CHECK(name,expr)      CHECK_BODY(name){ result = (expr); }\n\n// Print summary of test. Return value can be directly use as a return value for main().\nstatic inline int print_test_summary(void)\n{\n  fprintf(stderr,\"\\n\\n---------------------------------------------\\n\"\n                 \"succeeded: %i\\n\"\n                 \"failed   : %i\\n\\n\", ok, failed);\n  return failed;\n}\n\n#endif // TESTHELPER_H_\n"
  }
]