diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8a6c241 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Preserve upstream document bytes and original whitespace on every platform. +upstream/** -text -whitespace diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..35f2816 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + mcpp: + name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }}) + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' } + - { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' } + - { os: macos-15, toolchain: 'llvm@23.1.3' } + - { os: windows-2025, toolchain: 'llvm@23.1.3' } + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + defaults: + run: + shell: bash + env: + XLINGS_NON_INTERACTIVE: 1 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Mwrap identity and bilingual README contract + run: python3 tools/check_mwrap_release.py + + - name: Install Xlings (Linux/macOS) + if: runner.os != 'Windows' + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + + - name: Install Xlings (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex + "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Install project tools (.xlings.json) + run: | + for attempt in 1 2 3; do + xlings update > /dev/null 2>&1 || true + if xlings install; then break; fi + [ "$attempt" = 3 ] && exit 1 + sleep 30 + done + mcpp --version + + - name: Select the toolchain + run: | + name="${{ matrix.toolchain }}" + mcpp toolchain install "${name%@*}" "${name#*@}" + mcpp toolchain default "$name" + + - name: Build the modules + run: mcpp build + + - name: Tests (allocation, heaps, options, the STL allocator, statistics, threads, the constants, header mixing) + run: mcpp test + + - name: Export audit (every function, type and enumerator of the three headers, every macro) + run: python3 tools/check_exports.py + + - name: Upstream's test-api and test-stress over the same library + run: python3 tools/run_conformance.py --toolchain "${{ matrix.toolchain }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f0c6447 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Release +on: + push: + tags: ["mwrap-*"] +permissions: + contents: read +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + actions: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Check tag, upstream version and bilingual entry pages + run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME" + - name: Require successful source CI at this exact commit + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"') + test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; } + - name: Build immutable source asset and publish + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#mwrap-}" + repo="${GITHUB_REPOSITORY#*/}" + asset="${repo}-${version}.tar.gz" + git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset" + (cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256") + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo 'Release already exists; refusing to change immutable assets'; exit 1 + fi + extra=() + if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi + gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \ + --repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \ + --notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}" diff --git a/.github/workflows/templates.yml b/.github/workflows/templates.yml new file mode 100644 index 0000000..ce444ca --- /dev/null +++ b/.github/workflows/templates.yml @@ -0,0 +1,28 @@ +name: Actual project templates +on: + push: + branches: [main] + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: templates-${{ github.ref }} + cancel-in-progress: true +env: + MCPP_VERSION: "2026.11.2.1" +jobs: + templates: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Install the pinned mcpp + shell: bash + run: | + archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" + curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive" + tar -xzf "$archive" + echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH" + - name: Actual scaffold, strict build and run of the default template + run: python3 tools/test_template.py --toolchain gcc@16.1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c7b405 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +.mcpp/ +mcpp.lock +compile_commands.json +__pycache__/ +.xlings/ diff --git a/.xlings.json b/.xlings.json new file mode 100644 index 0000000..a18d5fe --- /dev/null +++ b/.xlings.json @@ -0,0 +1,5 @@ +{ + "workspace": { + "xim:mcpp": "2026.11.2.1" + } +} diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..1da63ef --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +mimalloc-m: C++ module wrapper +Copyright (c) 2026 mcpp-community contributors + +The wrapper contributions are licensed under the Apache License, Version 2.0. +Upstream code, copied interfaces and dependencies retain their original +licenses and copyright notices. mimalloc is distributed under the MIT license: + + Copyright (c) 2018-2026 Microsoft Corporation, Daan Leijen. + +Its original README and license are retained in upstream/mimalloc/. The +library itself is provided by compat.mimalloc from the same release, and the +typed macros of include/mwrap/mimalloc/macros.hpp are upstream's definitions. +Provenance and checksums are recorded in upstream/documents.json. diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..3e14330 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,73 @@ +# mimalloc + +> mimalloc——微软的通用内存分配器,以 C++23 模块提供。 + +[English](README.md) · **简体中文** · [上游](https://github.com/microsoft/mimalloc) · [发布](https://github.com/mcpplibs/mimalloc-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [索引](https://github.com/mcpplibs/mcpp-index) + +[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) +[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community) +[![CI](https://github.com/mcpplibs/mimalloc-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/mimalloc-m/actions/workflows/ci.yml) + +上游 API 不变:`mi_*` 函数、`mi_heap_t`、选项枚举、回调类型与 STL 分配器都是 `` 中的上游实体,位于上游声明它们的 +全局命名空间。库本身是 `compat.mimalloc` 3.6.1;值宏映射为常量。每次 CI 都在同一份库上运行上游自己的 `test-api` 与 +`test-stress`。 + +## 模块 + +- `import mcpplibs.mwrap.mimalloc;`:`` 的函数、堆、arena、子进程、选项与 STL 分配器,并重新导出下面三个模块 +- `import mcpplibs.mwrap.mimalloc.stats;`:`mi_stats_get`、`mi_heap_stats_get` 等与 `mi_stats_t`(``) +- `import mcpplibs.mwrap.mimalloc.profile;`:采样剖析器与 pprof 写出(``) +- `import mcpplibs.mwrap.mimalloc.macros;`:`MI_MALLOC_VERSION`、`MI_SMALL_SIZE_MAX` 等,位于 `mcpplibs::mwrap::mimalloc::macros` + +> 更多模块 API -> [用法与 API](docs/usage.md) + +## 快速开始 + +```sh +mcpp new allocate --template mcpplibs.mwrap.mimalloc@3.6.1 +cd allocate +mcpp run +``` + +默认模板在一个私有堆中分配并一次销毁,分配一个对齐的块,让 vector 的元素由 mimalloc 分配,并打印版本。 + +```toml +[dependencies] +"mcpplibs.mwrap.mimalloc" = "3.6.1" +``` + +```cpp +import std; +import mcpplibs.mwrap.mimalloc; + +int main() { + mi_heap_t* heap = mi_heap_new(); + auto* p = static_cast(mi_heap_malloc(heap, 64)); + std::println("{} usable bytes", mi_usable_size(p)); + mi_heap_destroy(heap); // 释放这个堆中的全部块 + std::vector> v{1, 2, 3}; +} +``` + +## 配置与宏 + +mimalloc 在构建 `compat.mimalloc` 时配置;运行时的选项经 `mi_option_set` 或 `MIMALLOC_*` 环境变量设置。带类型参数的分配宏 +(`mi_malloc_tp(T)`、`mi_heap_calloc_tp(T, heap, n)` 等)没有调用形式相同的函数可以替代,保留为 +`` 中的宏,在 import 之前包含;其中三个调用上游的 `static inline` 辅助函数,模块无法导出它们,模块以 +`mcpplibs::mwrap::mimalloc::mi_malloc_csize` 等同名函数适配。宏在限定名中同样会展开,因此与 `` 同时使用时要换名 +读取常量([docs/usage.md](docs/usage.md))。 + +本包不替换 `malloc`:与 `compat.mimalloc` 相同,构建时不定义 `MI_MALLOC_OVERRIDE`。 + +## 构建与测试 + +Linux、macOS 与 Windows 的 CI 构建模块并运行测试:分配、重新分配、清零、对齐、私有堆及其遍历、类型宏、选项、STL 分配器、 +统计、四个线程同时分配、常量与头文件混用。CI 还运行导出审计([tools/check_exports.py](tools/check_exports.py):三个头文件的 +每个函数与类型都已导出、适配或列出理由),以及在同一份库上构建的上游 `test/test-api.c` 与 `test/test-stress.c` +([tools/run_conformance.py](tools/run_conformance.py))。另有一个作业以已安装包的方式生成默认模板并运行。 + +## 许可 + +封装部分为 [Apache-2.0](LICENSE),作者 mcpp-community([NOTICE](NOTICE))。mimalloc 仍为 MIT 许可 +([upstream/mimalloc/LICENSE](upstream/mimalloc/LICENSE)、[README](upstream/mimalloc/README.md))。 diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..7ae3fa8 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,55 @@ +# Usage and API + +`mcpplibs.mwrap.mimalloc` 3.6.1 exports mimalloc's public API, upstream's names +unchanged. This page lists what each module holds and the places where a module +and a header differ. + +## Modules + +| Module | Holds | +|---|---| +| `mcpplibs.mwrap.mimalloc` | the functions of `` (allocation, aligned allocation, heaps, arenas, sub-processes, options, process and thread control, the POSIX and C++ `new` forms); `mi_heap_t`, `mi_heap_area_t`, `mi_theap_t`, `mi_arena_id_t`, `mi_subproc_id_t`, the callback types, `mi_option_t` and its enumerators; `mi_stl_allocator`, `mi_heap_stl_allocator` and `mi_heap_destroy_stl_allocator` with their comparisons; re-exports the three below | +| `mcpplibs.mwrap.mimalloc.stats` | ``: `mi_stats_get`, `mi_heap_stats_get`, `mi_subproc_stats_get` and their JSON and text forms; `mi_stats_t` and its counters; `mi_chunkbin_t` | +| `mcpplibs.mwrap.mimalloc.profile` | ``: `mi_profiler_t` and its callbacks, `mi_heap_profile`, `mi_profile`, `mi_profiler_start` / `stop` / `snapshot`, `mi_pprof_profiler_new` / `delete` | +| `mcpplibs.mwrap.mimalloc.macros` | the value macros as constants in `mcpplibs::mwrap::mimalloc::macros` | + +## What a module cannot export, and what stands in for it + +- **`static inline` functions.** `` defines nine (`mi_wsize_from_size`, + `mi_malloc_csize`, `mi_zalloc_csize`, `mi_theap_malloc_csize`, + `mi_theap_zalloc_csize`, `mi_free_csize` and its three variants), and + `` two (`mi_stats_header_init`, `mi_stats_init`). A function + with internal linkage belongs to one translation unit, so each is adapted under + its name in `mcpplibs::mwrap::mimalloc` and calls upstream's body. +- **Macros that take a type.** `mi_malloc_tp(T)`, `mi_zalloc_tp(T)`, + `mi_calloc_tp(T, n)`, `mi_mallocn_tp`, `mi_reallocn_tp`, `mi_recalloc_tp`, + `mi_free_tp` and the six `mi_heap_*_tp`, and `mi_stats_t_decl(name)`, stay macros + in ``. Include it before the imports; the macros + that call an adapted helper name it with its namespace. +- **Value macros.** `MI_MALLOC_VERSION`, `MI_SMALL_WSIZE_MAX`, `MI_SMALL_SIZE_MAX`, + `MI_STAT_VERSION`, `MI_BIN_HUGE`, `MI_PROFILE_SAMPLE_DATA_MAX_SIZE` and + `MI_HAS_HEAP_STL_ALLOCATOR` are `inline constexpr` constants of the same names. + +## A header beside the module + +`#include ` and `import mcpplibs.mwrap.mimalloc;` may appear in one +unit: they declare the same entities. A macro expands even inside a qualified +name, so `mcpplibs::mwrap::mimalloc::macros::MI_MALLOC_VERSION` written after the +header is `...::macros::30601`. Read the header's value under another name and +`#undef` the macro before naming the constant (tests/macros.cpp does this). + +## Configuration + +The library is `compat.mimalloc` 3.6.1, built by mcpp from the release tarball +without `MI_MALLOC_OVERRIDE`: `malloc` remains the C library's. Options are set at +run time with `mi_option_set` or the `MIMALLOC_*` environment variables +(`MIMALLOC_SHOW_STATS=1`, `MIMALLOC_VERBOSE=1` …), as upstream documents. + +## Testing + +```sh +mcpp test # the module: API, constants, header mixing +python3 tools/check_exports.py # every entity and macro accounted for +python3 tools/run_conformance.py # upstream's test-api and test-stress +python3 tools/test_template.py # the default template, as installed +``` diff --git a/include/mwrap/mimalloc/macros.hpp b/include/mwrap/mimalloc/macros.hpp new file mode 100644 index 0000000..bc8bcd9 --- /dev/null +++ b/include/mwrap/mimalloc/macros.hpp @@ -0,0 +1,37 @@ +// --- the macros of that take a type +// (`mi_malloc_tp(T)' and its eleven companions), which a named module cannot +// export and no function can replace with the same call syntax. Include it +// before `import mcpplibs.mwrap.mimalloc;'; each expands to the upstream +// functions the module exports, and the three that upstream spells with its +// `static inline' helpers to the module's adaptations of them +// (mcpplibs::mwrap::mimalloc::mi_malloc_csize, mi_zalloc_csize, +// mi_free_csize). The definitions are otherwise upstream's (mimalloc 3.6.1, +// include/mimalloc.h, MIT license, upstream/mimalloc/LICENSE), each defined +// only where has not defined it already. +#ifndef MWRAP_MIMALLOC_MACROS_HPP +#define MWRAP_MIMALLOC_MACROS_HPP + +#ifndef mi_malloc_tp +#define mi_malloc_tp(tp) ((tp*)::mcpplibs::mwrap::mimalloc::mi_malloc_csize(sizeof(tp))) +#define mi_zalloc_tp(tp) ((tp*)::mcpplibs::mwrap::mimalloc::mi_zalloc_csize(sizeof(tp))) +#define mi_calloc_tp(tp,n) ((tp*)mi_calloc(n,sizeof(tp))) +#define mi_mallocn_tp(tp,n) ((tp*)mi_mallocn(n,sizeof(tp))) +#define mi_reallocn_tp(tp,p,n) ((tp*)mi_reallocn(p,n,sizeof(tp))) +#define mi_recalloc_tp(tp,p,n) ((tp*)mi_recalloc(p,n,sizeof(tp))) +#define mi_free_tp(tp,p) (::mcpplibs::mwrap::mimalloc::mi_free_csize(p,sizeof(tp))) + +#define mi_heap_malloc_tp(tp,hp) ((tp*)mi_heap_malloc(hp,sizeof(tp))) +#define mi_heap_zalloc_tp(tp,hp) ((tp*)mi_heap_zalloc(hp,sizeof(tp))) +#define mi_heap_calloc_tp(tp,hp,n) ((tp*)mi_heap_calloc(hp,n,sizeof(tp))) +#define mi_heap_mallocn_tp(tp,hp,n) ((tp*)mi_heap_mallocn(hp,n,sizeof(tp))) +#define mi_heap_reallocn_tp(tp,hp,p,n) ((tp*)mi_heap_reallocn(hp,p,n,sizeof(tp))) +#define mi_heap_recalloc_tp(tp,hp,p,n) ((tp*)mi_heap_recalloc(hp,p,n,sizeof(tp))) +#endif + +// : a statistics structure declared and initialised, through +// the module's adaptation of upstream's `static inline' mi_stats_init. +#ifndef mi_stats_t_decl +#define mi_stats_t_decl(name) mi_stats_t name; ::mcpplibs::mwrap::mimalloc::mi_stats_init(&name); +#endif + +#endif diff --git a/mcpp.toml b/mcpp.toml new file mode 100644 index 0000000..9390798 --- /dev/null +++ b/mcpp.toml @@ -0,0 +1,43 @@ +# Module wrapper; upstream API preserved. Policy: mcpp-index/docs/mwrap.md. +[package] +namespace = "mcpplibs.mwrap" +name = "mimalloc" +version = "3.6.1" +description = "mimalloc 3.6.1 C++ modules for mcpp (import mcpplibs.mwrap.mimalloc;): Microsoft's general-purpose allocator over compat.mimalloc, its heaps, options, statistics, profiler and STL allocators" +license = "Apache-2.0" +authors = ["mcpp-community"] +repo = "https://github.com/mcpplibs/mimalloc-m" +platforms = ["linux", "macos", "windows"] + +[targets.mimalloc] +kind = "lib" + +[build] +sources = ["src/*.cppm"] +# : the typed macros (mi_malloc_tp …). +include_dirs = ["include"] + +[dependencies] +compat.mimalloc = "3.6.1" + +[test] +discover = ["tests/*.cpp"] + +[package.metadata.mwrap] +upstream-version = "3.6.1" + +[[runtime.deploy]] +from = "LICENSE" +to = "licenses/mimalloc" + +[[runtime.deploy]] +from = "NOTICE" +to = "licenses/mimalloc" + +[[runtime.deploy]] +from = "upstream/mimalloc/README.md" +to = "licenses/mimalloc/upstream/mimalloc" + +[[runtime.deploy]] +from = "upstream/mimalloc/LICENSE" +to = "licenses/mimalloc/upstream/mimalloc" diff --git a/src/macros.cppm b/src/macros.cppm new file mode 100644 index 0000000..7646a4a --- /dev/null +++ b/src/macros.cppm @@ -0,0 +1,54 @@ +// mcpplibs.mwrap.mimalloc.macros --- the macros of mimalloc's headers that are +// values (a named module cannot export a macro), under their original names in +// mcpplibs::mwrap::mimalloc::macros, each `inline constexpr' and equal to the +// macro where the module is built: +// +// MI_MALLOC_VERSION major, two digits of minor, two of patch +// MI_SMALL_WSIZE_MAX, MI_SMALL_SIZE_MAX the largest "small" allocation, +// in words and in bytes +// MI_STAT_VERSION, MI_BIN_HUGE the statistics structure's version and +// its last size bin () +// MI_PROFILE_SAMPLE_DATA_MAX_SIZE the largest profiler sample data +// () +// MI_HAS_HEAP_STL_ALLOCATOR the heap STL allocators are declared +// +// The function-like macros that take a type (mi_malloc_tp, mi_heap_calloc_tp +// …) cannot be functions with the same call syntax: they stay macros, in +// . tools/check_exports.py accounts for every macro. +module; +#include +#include +#include +#include + +// The values, taken under other names while the macros are defined; then the +// macros are undefined, so that the names below are declared rather than +// expanded. +namespace mwrap_mimalloc_capture { +inline constexpr int value_MI_MALLOC_VERSION = MI_MALLOC_VERSION; +inline constexpr size_t value_MI_SMALL_WSIZE_MAX = MI_SMALL_WSIZE_MAX; +inline constexpr size_t value_MI_SMALL_SIZE_MAX = MI_SMALL_SIZE_MAX; +inline constexpr int value_MI_STAT_VERSION = MI_STAT_VERSION; +inline constexpr unsigned value_MI_BIN_HUGE = MI_BIN_HUGE; +inline constexpr size_t value_MI_PROFILE_SAMPLE_DATA_MAX_SIZE = MI_PROFILE_SAMPLE_DATA_MAX_SIZE; +inline constexpr bool value_MI_HAS_HEAP_STL_ALLOCATOR = (MI_HAS_HEAP_STL_ALLOCATOR) != 0; +} +#undef MI_MALLOC_VERSION +#undef MI_SMALL_WSIZE_MAX +#undef MI_SMALL_SIZE_MAX +#undef MI_STAT_VERSION +#undef MI_BIN_HUGE +#undef MI_PROFILE_SAMPLE_DATA_MAX_SIZE +#undef MI_HAS_HEAP_STL_ALLOCATOR + +export module mcpplibs.mwrap.mimalloc.macros; + +export namespace mcpplibs::mwrap::mimalloc::macros { +inline constexpr int MI_MALLOC_VERSION = mwrap_mimalloc_capture::value_MI_MALLOC_VERSION; +inline constexpr size_t MI_SMALL_WSIZE_MAX = mwrap_mimalloc_capture::value_MI_SMALL_WSIZE_MAX; +inline constexpr size_t MI_SMALL_SIZE_MAX = mwrap_mimalloc_capture::value_MI_SMALL_SIZE_MAX; +inline constexpr int MI_STAT_VERSION = mwrap_mimalloc_capture::value_MI_STAT_VERSION; +inline constexpr unsigned MI_BIN_HUGE = mwrap_mimalloc_capture::value_MI_BIN_HUGE; +inline constexpr size_t MI_PROFILE_SAMPLE_DATA_MAX_SIZE = mwrap_mimalloc_capture::value_MI_PROFILE_SAMPLE_DATA_MAX_SIZE; +inline constexpr bool MI_HAS_HEAP_STL_ALLOCATOR = mwrap_mimalloc_capture::value_MI_HAS_HEAP_STL_ALLOCATOR; +} diff --git a/src/mimalloc.cppm b/src/mimalloc.cppm new file mode 100644 index 0000000..6c1225d --- /dev/null +++ b/src/mimalloc.cppm @@ -0,0 +1,150 @@ +// mcpplibs.mwrap.mimalloc --- mimalloc 3.6.1, Microsoft's general-purpose +// allocator, as a C++ module. Every exported name is the upstream entity of +// , unchanged, in the global namespace where upstream declares it: +// the `mi_*' functions, the heap and arena types, the callback types, the +// option enumeration and its enumerators, and the STL allocators of C++. The +// root also re-exports the statistics (), the profiler +// () and the macro mappings. The implementation is +// compat.mimalloc 3.6.1; tools/check_exports.py audits these lists against the +// headers. +module; +#include + +export module mcpplibs.mwrap.mimalloc; + +export import mcpplibs.mwrap.mimalloc.stats; +export import mcpplibs.mwrap.mimalloc.profile; +export import mcpplibs.mwrap.mimalloc.macros; + +// The functions of . +export { +using ::mi_malloc; using ::mi_calloc; using ::mi_realloc; using ::mi_expand; +using ::mi_free; using ::mi_strdup; using ::mi_strndup; using ::mi_realpath; +using ::mi_malloc_small; using ::mi_zalloc_small; using ::mi_zalloc; using ::mi_mallocn; +using ::mi_reallocn; using ::mi_reallocf; using ::mi_usable_size; using ::mi_good_size; +using ::mi_free_size; using ::mi_free_small; using ::mi_free_small_nonnull; using ::mi_free_size_aligned; +using ::mi_free_aligned; using ::mi_cfree; using ::mi_malloc_aligned; using ::mi_malloc_aligned_at; +using ::mi_zalloc_aligned; using ::mi_zalloc_aligned_at; using ::mi_calloc_aligned; using ::mi_calloc_aligned_at; +using ::mi_realloc_aligned; using ::mi_realloc_aligned_at; using ::mi_register_deferred_free; using ::mi_register_output; +using ::mi_register_error; using ::mi_collect; using ::mi_version; using ::mi_options_print; +using ::mi_process_info_print; using ::mi_options_print_out; using ::mi_process_info_print_out; using ::mi_process_info; +using ::mi_process_init; using ::mi_process_done; using ::mi_thread_init; using ::mi_thread_done; +using ::mi_thread_set_in_threadpool; using ::mi_umalloc; using ::mi_ucalloc; using ::mi_urealloc; +using ::mi_ufree; using ::mi_umalloc_aligned; using ::mi_uzalloc_aligned; using ::mi_umalloc_small; +using ::mi_uzalloc_small; using ::mi_heap_new; using ::mi_heap_delete; using ::mi_heap_destroy; +using ::mi_heap_set_numa_affinity; using ::mi_heap_set_debug_fill_preserve; using ::mi_heap_collect; using ::mi_heap_main; +using ::mi_heap_of; using ::mi_heap_contains; using ::mi_any_heap_contains; using ::mi_heap_malloc; +using ::mi_heap_zalloc; using ::mi_heap_calloc; using ::mi_heap_mallocn; using ::mi_heap_malloc_small; +using ::mi_heap_zalloc_small; using ::mi_heap_realloc; using ::mi_heap_reallocn; using ::mi_heap_reallocf; +using ::mi_heap_strdup; using ::mi_heap_strndup; using ::mi_heap_realpath; using ::mi_heap_malloc_aligned; +using ::mi_heap_malloc_aligned_at; using ::mi_heap_zalloc_aligned; using ::mi_heap_zalloc_aligned_at; using ::mi_heap_calloc_aligned; +using ::mi_heap_calloc_aligned_at; using ::mi_heap_realloc_aligned; using ::mi_heap_realloc_aligned_at; using ::mi_rezalloc; +using ::mi_recalloc; using ::mi_rezalloc_aligned; using ::mi_rezalloc_aligned_at; using ::mi_recalloc_aligned; +using ::mi_recalloc_aligned_at; using ::mi_heap_rezalloc; using ::mi_heap_recalloc; using ::mi_heap_rezalloc_aligned; +using ::mi_heap_rezalloc_aligned_at; using ::mi_heap_recalloc_aligned; using ::mi_heap_recalloc_aligned_at; using ::mi_heap_visit_blocks; +using ::mi_heap_visit_abandoned_blocks; using ::mi_is_redirected; using ::mi_reserve_huge_os_pages_interleave; using ::mi_reserve_huge_os_pages_at; +using ::mi_reserve_os_memory; using ::mi_manage_os_memory; using ::mi_debug_show_arenas; using ::mi_arenas_print; +using ::mi_arenas_purge; using ::mi_arena_min_alignment; using ::mi_arena_min_size; using ::mi_arena_max_object_size; +using ::mi_arena_area; using ::mi_reserve_huge_os_pages_at_ex; using ::mi_reserve_os_memory_ex; using ::mi_manage_os_memory_ex; +using ::mi_arena_contains; using ::mi_heap_new_in_arena; using ::mi_subproc_main; using ::mi_subproc_current; +using ::mi_subproc_new; using ::mi_subproc_destroy; using ::mi_subproc_add_current_thread; using ::mi_subproc_visit_heaps; +using ::mi_heap_theap; using ::mi_theap_set_default; using ::mi_theap_get_default; using ::mi_theap_collect; +using ::mi_theap_malloc; using ::mi_theap_zalloc; using ::mi_theap_calloc; using ::mi_theap_malloc_small; +using ::mi_theap_zalloc_small; using ::mi_theap_malloc_aligned; using ::mi_theap_zalloc_aligned; using ::mi_theap_realloc; +using ::mi_theap_rezalloc; using ::mi_theap_guarded_set_sample_rate; using ::mi_theap_guarded_set_size_bound; using ::mi_manage_memory; +using ::mi_unsafe_heap_page_is_under_utilized; using ::mi_theap_delete; using ::mi_theap_quiescent_abandon; using ::mi_thread_id_of; +using ::mi_check_owned; using ::mi_thread_stats_print_out; using ::mi_is_in_heap_region; using ::mi_theap_visit_blocks; +using ::mi_reserve_huge_os_pages; using ::mi_collect_reduce; using ::mi_stats_reset; using ::mi_stats_merge; +using ::mi_stats_print; using ::mi_stats_print_out; using ::mi_option_is_enabled; using ::mi_option_enable; +using ::mi_option_disable; using ::mi_option_set_enabled; using ::mi_option_set_enabled_default; using ::mi_option_get; +using ::mi_option_get_clamp; using ::mi_option_get_size; using ::mi_option_set; using ::mi_option_set_default; +using ::mi_malloc_size; using ::mi_malloc_good_size; using ::mi_malloc_usable_size; using ::mi_posix_memalign; +using ::mi_memalign; using ::mi_valloc; using ::mi_pvalloc; using ::mi_reallocarray; +using ::mi_reallocarr; using ::mi_aligned_recalloc; using ::mi_aligned_offset_recalloc; using ::mi_aligned_alloc; +using ::mi_free_aligned_sized; using ::mi_free_sized; using ::mi__expand; using ::mi_dupenv_s; +using ::mi_wdupenv_s; using ::mi_wcsdup; using ::mi_mbsdup; using ::mi_new; +using ::mi_new_aligned; using ::mi_new_nothrow; using ::mi_new_aligned_nothrow; using ::mi_new_n; +using ::mi_new_realloc; using ::mi_new_reallocn; using ::mi_heap_alloc_new; using ::mi_heap_alloc_new_n; +using ::mi_theap_alloc_new; using ::mi_theap_alloc_new_n; using ::mi_theap_alloc_new_nothrow; using ::mi_free_small_local; +using ::mi_free_small_local_nonnull; +} + +// Declared without upstream's export attribute, and extern all the same. +export { +using ::mi_wzalloc_small; using ::mi_wmalloc_small; using ::mi_theap_wmalloc_small; using ::mi_theap_wzalloc_small; +} + +// UPSTREAM'S `static inline' FUNCTIONS, adapted. A function with internal +// linkage is local to a translation unit and cannot be exported, so each is +// declared here under its own name in mcpplibs::mwrap::mimalloc and calls +// upstream's body (below). spells the typed +// macros with these names. +export namespace mcpplibs::mwrap::mimalloc { +size_t mi_wsize_from_size(size_t size) noexcept; +void* mi_malloc_csize(size_t size) noexcept; +void* mi_zalloc_csize(size_t size) noexcept; +void* mi_theap_malloc_csize(mi_theap_t* theap, size_t size) noexcept; +void* mi_theap_zalloc_csize(mi_theap_t* theap, size_t size) noexcept; +void mi_free_csize(void* p, size_t size) noexcept; +void mi_free_csize_nonnull(void* p, size_t size) noexcept; +void mi_free_csize_aligned(void* p, size_t size, size_t aligned) noexcept; +void mi_free_csize_aligned_nonnull(void* p, size_t size, size_t aligned) noexcept; +} + +// Its types. +export { +using ::mi_heap_t; using ::mi_heap_s; using ::mi_heap_area_t; using ::mi_heap_area_s; +using ::mi_theap_t; using ::mi_theap_s; using ::mi_arena_id_t; +using ::mi_subproc_id_t; using ::mi_option_t; using ::mi_option_e; +using ::mi_deferred_free_fun; using ::mi_output_fun; using ::mi_error_fun; +using ::mi_block_visit_fun; using ::mi_heap_visit_fun; using ::mi_commit_fun_t; +} + +// The options, mi_option_t's enumerators (deprecated spellings included, as +// upstream keeps them). +export { +using ::mi_option_show_errors; using ::mi_option_show_stats; using ::mi_option_verbose; +using ::mi_option_deprecated_eager_commit; using ::mi_option_arena_eager_commit; using ::mi_option_purge_decommits; +using ::mi_option_allow_large_os_pages; using ::mi_option_reserve_huge_os_pages; using ::mi_option_reserve_huge_os_pages_at; +using ::mi_option_reserve_os_memory; using ::mi_option_deprecated_segment_cache; using ::mi_option_deprecated_page_reset; +using ::mi_option_deprecated_abandoned_page_purge; using ::mi_option_deprecated_segment_reset; using ::mi_option_deprecated_eager_commit_delay; +using ::mi_option_purge_delay; using ::mi_option_use_numa_nodes; using ::mi_option_disallow_os_alloc; +using ::mi_option_os_tag; using ::mi_option_max_errors; using ::mi_option_max_warnings; +using ::mi_option_deprecated_max_segment_reclaim; using ::mi_option_destroy_on_exit; using ::mi_option_arena_reserve; +using ::mi_option_arena_purge_mult; using ::mi_option_deprecated_purge_extend_delay; using ::mi_option_disallow_arena_alloc; +using ::mi_option_retry_on_oom; using ::mi_option_deprecated_visit_abandoned; using ::mi_option_guarded_min; +using ::mi_option_guarded_max; using ::mi_option_unsafe_guarded_precise; using ::mi_option_guarded_sample_rate; +using ::mi_option_guarded_sample_seed; using ::mi_option_generic_collect; using ::mi_option_page_reclaim_on_free; +using ::mi_option_page_full_retain; using ::mi_option_page_max_candidates; using ::mi_option_max_vabits; +using ::mi_option_pagemap_commit; using ::mi_option_page_commit_on_demand; using ::mi_option_page_max_reclaim; +using ::mi_option_page_cross_thread_max_reclaim; using ::mi_option_allow_thp; using ::mi_option_arena_purge_min_size; +using ::mi_option_arena_max_object_size; using ::mi_option_arena_is_numa_local; using ::mi_option_stats_merge_on_collect; +using ::mi_option_profile_alloc_interval; using ::mi_option_profile_inuse_interval; using ::mi_option_profile_time_interval; +using ::mi_option_profile_sample_rate; using ::mi_option_stats_merge_threshold; using ::mi_option_arena_purge_immediate_size; +using ::_mi_option_last; using ::mi_option_large_os_pages; using ::mi_option_eager_region_commit; +using ::mi_option_reset_decommits; using ::mi_option_reset_delay; using ::mi_option_limit_os_alloc; +using ::mi_option_minimal_purge_size; +} + +// The allocators of C++ (upstream's MI_HAS_HEAP_STL_ALLOCATOR), with their +// comparisons. +export { +using ::mi_stl_allocator; +using ::mi_heap_stl_allocator; +using ::mi_heap_destroy_stl_allocator; +using ::operator==; +using ::operator!=; +} + +namespace mcpplibs::mwrap::mimalloc { +size_t mi_wsize_from_size(size_t size) noexcept { return ::mi_wsize_from_size(size); } +void* mi_malloc_csize(size_t size) noexcept { return ::mi_malloc_csize(size); } +void* mi_zalloc_csize(size_t size) noexcept { return ::mi_zalloc_csize(size); } +void* mi_theap_malloc_csize(mi_theap_t* theap, size_t size) noexcept { return ::mi_theap_malloc_csize(theap, size); } +void* mi_theap_zalloc_csize(mi_theap_t* theap, size_t size) noexcept { return ::mi_theap_zalloc_csize(theap, size); } +void mi_free_csize(void* p, size_t size) noexcept { ::mi_free_csize(p, size); } +void mi_free_csize_nonnull(void* p, size_t size) noexcept { ::mi_free_csize_nonnull(p, size); } +void mi_free_csize_aligned(void* p, size_t size, size_t aligned) noexcept { ::mi_free_csize_aligned(p, size, aligned); } +void mi_free_csize_aligned_nonnull(void* p, size_t size, size_t aligned) noexcept { ::mi_free_csize_aligned_nonnull(p, size, aligned); } +} + diff --git a/src/profile.cppm b/src/profile.cppm new file mode 100644 index 0000000..16a9c60 --- /dev/null +++ b/src/profile.cppm @@ -0,0 +1,17 @@ +// mcpplibs.mwrap.mimalloc.profile --- : sampling +// profilers of a heap, a subprocess or the process, and upstream's pprof +// writer. The upstream names, unchanged. +module; +#include +#include + +export module mcpplibs.mwrap.mimalloc.profile; + +export { +using ::mi_heap_profile; using ::mi_heap_profile_disable; using ::mi_subproc_profile; using ::mi_profile; +using ::mi_profiler_start; using ::mi_profiler_stop; using ::mi_profiler_snapshot; using ::mi_pprof_profiler_new; +using ::mi_pprof_profiler_delete; +using ::mi_profiler_t; using ::mi_profiler_s; using ::mi_profiler_sample_data_t; using ::mi_profiler_sample_data_s; +using ::mi_profiler_on_alloc_fun; using ::mi_profiler_on_realloc_inplace_fun; +using ::mi_profiler_on_free_fun; using ::mi_profiler_on_snapshot_fun; +} diff --git a/src/stats.cppm b/src/stats.cppm new file mode 100644 index 0000000..b9a0713 --- /dev/null +++ b/src/stats.cppm @@ -0,0 +1,34 @@ +// mcpplibs.mwrap.mimalloc.stats --- : a heap's, a +// subprocess's and the process's statistics as a structure, as JSON and as +// text. The upstream names, unchanged; the two `static inline' initialisers +// adapted in mcpplibs::mwrap::mimalloc, and `mi_stats_t_decl(name)' kept in +// . +module; +#include +#include + +export module mcpplibs.mwrap.mimalloc.stats; + +export { +using ::mi_heap_stats_get; using ::mi_heap_stats_get_json; using ::mi_heap_stats_print_out; using ::mi_subproc_stats_get; +using ::mi_subproc_stats_get_json; using ::mi_subproc_stats_print_out; using ::mi_subproc_heap_stats_print_out; using ::mi_stats_get; +using ::mi_stats_get_json; using ::mi_heap_stats_merge_to_subproc; using ::mi_subproc_stats_get_exclusive; using ::mi_stats_as_json; +using ::mi_stats_get_bin_size; using ::mi_theap_stats_get; using ::mi_theap_stats_merge_to_heap; +using ::mi_stats_t; using ::mi_stats_s; using ::mi_stat_count_t; using ::mi_stat_count_s; +using ::mi_stat_counter_t; using ::mi_stat_counter_s; using ::mi_chunkbin_t; using ::mi_chunkbin_e; +using ::MI_CBIN_SMALL; using ::MI_CBIN_OTHER; using ::MI_CBIN_MEDIUM; using ::MI_CBIN_LARGE; +using ::MI_CBIN_HUGE; using ::MI_CBIN_NONE; using ::MI_CBIN_COUNT; +} + +// Upstream's `static inline' helpers of , adapted under +// their names (a function local to a translation unit cannot be exported). +export namespace mcpplibs::mwrap::mimalloc { +void mi_stats_header_init(mi_stats_t* stats) noexcept; +void mi_stats_init(mi_stats_t* stats) noexcept; +} + +namespace mcpplibs::mwrap::mimalloc { +void mi_stats_header_init(mi_stats_t* stats) noexcept { ::mi_stats_header_init(stats); } +void mi_stats_init(mi_stats_t* stats) noexcept { ::mi_stats_init(stats); } +} + diff --git a/templates/allocate/mcpp.toml.in b/templates/allocate/mcpp.toml.in new file mode 100644 index 0000000..a637560 --- /dev/null +++ b/templates/allocate/mcpp.toml.in @@ -0,0 +1,10 @@ +[package] +name = "{{project.name}}" +version = "0.1.0" + +[targets.{{project.name}}] +kind = "bin" +main = "src/main.cpp" + +[dependencies] +"{{self.name}}" = "{{self.version}}" diff --git a/templates/allocate/src/main.cpp.in b/templates/allocate/src/main.cpp.in new file mode 100644 index 0000000..62da288 --- /dev/null +++ b/templates/allocate/src/main.cpp.in @@ -0,0 +1,31 @@ +// {{project.name}}: allocation with mimalloc through C++ modules. +import std; +import mcpplibs.mwrap.mimalloc; + +int main(int argc, char** argv) +{ + const int blocks = argc > 1 ? std::atoi(argv[1]) : 100000; + + // A private heap: everything allocated in it goes when it is destroyed. + mi_heap_t* heap = mi_heap_new(); + std::size_t bytes = 0; + for (int i = 0; i < blocks; ++i) { + const std::size_t size = 16 + static_cast(i % 1024); + void* p = mi_heap_malloc(heap, size); + bytes += mi_usable_size(p); + } + std::println("{} blocks, {} usable bytes, in one heap", blocks, bytes); + mi_heap_destroy(heap); + + // An aligned block. + void* page = mi_malloc_aligned(4096, 4096); + std::println("an aligned block at {} (4096-aligned: {})", page, reinterpret_cast(page) % 4096 == 0); + mi_free(page); + + // A container whose elements are mimalloc's. + std::vector> v(blocks); + std::iota(v.begin(), v.end(), 0); + std::println("sum of {} ints in a mimalloc vector: {}", v.size(), std::accumulate(v.begin(), v.end(), 0LL)); + + std::println("mimalloc {}", mi_version()); +} diff --git a/templates/allocate/template.toml b/templates/allocate/template.toml new file mode 100644 index 0000000..d0521fd --- /dev/null +++ b/templates/allocate/template.toml @@ -0,0 +1,4 @@ +[template] +description = "Allocation with import mcpplibs.mwrap.mimalloc: a private heap, aligned blocks, an STL container over mimalloc, and the statistics" +default = true +post_message = "Run `mcpp run`, or `mcpp run -- `." diff --git a/tests/api_test.cpp b/tests/api_test.cpp new file mode 100644 index 0000000..f10c291 --- /dev/null +++ b/tests/api_test.cpp @@ -0,0 +1,89 @@ +// The allocator through import mcpplibs.mwrap.mimalloc: each check can fail, +// and each names what the allocator promises rather than that a call returned. +#include +import std; +import mcpplibs.mwrap.mimalloc; + +namespace { +int failures = 0; +void check(bool ok, std::string_view what) +{ + std::println("{} {}", ok ? "ok " : "FAIL", what); + if (!ok) ++failures; +} +struct point { double x, y, z; }; +} + +int main() +{ + void* p = mi_malloc(1000); + check(p != nullptr && mi_usable_size(p) >= 1000, "mi_malloc hands out at least what was asked, and knows its size"); + std::memset(p, 0x5a, 1000); + p = mi_realloc(p, 100000); + check(p != nullptr && static_cast(p)[999] == 0x5a, "mi_realloc keeps the bytes it moves"); + mi_free(p); + + auto* z = static_cast(mi_zalloc(4096)); + check(z && std::all_of(z, z + 4096, [](unsigned char c) { return c == 0; }), "mi_zalloc returns zeroed memory"); + mi_free(z); + + for (std::size_t align : {16u, 64u, 4096u, 65536u}) { + void* a = mi_malloc_aligned(100, align); + check(a && reinterpret_cast(a) % align == 0, std::format("mi_malloc_aligned honours {}", align)); + mi_free(a); + } + + char* s = mi_strdup("mimalloc"); + check(s && std::string_view(s) == "mimalloc", "mi_strdup copies a string"); + mi_free(s); + + mi_heap_t* heap = mi_heap_new(); + check(heap != nullptr, "a private heap is made"); + std::vector blocks; + for (int i = 0; i < 1000; ++i) blocks.push_back(mi_heap_malloc(heap, 64 + i)); + check(std::all_of(blocks.begin(), blocks.end(), [&](void* b) { return mi_heap_contains(heap, b); }), + "the heap holds every block it handed out"); + std::size_t visited = 0; + mi_heap_visit_blocks(heap, true, [](const mi_heap_t*, const mi_heap_area_t*, void* block, std::size_t, void* arg) { + if (block) ++*static_cast(arg); + return true; + }, &visited); + check(visited == blocks.size(), "visiting the heap finds every block"); + mi_heap_destroy(heap); + + point* pt = mi_malloc_tp(point); + check(pt != nullptr, "mi_malloc_tp, a macro of , allocates its type"); + pt->x = 1; + mi_free_tp(point, pt); + + check(mi_version() == mcpplibs::mwrap::mimalloc::macros::MI_MALLOC_VERSION, "mi_version is the version the module was built from"); + + const long before = mi_option_get(mi_option_purge_delay); + mi_option_set(mi_option_purge_delay, before + 1); + check(mi_option_get(mi_option_purge_delay) == before + 1, "an option is set and read back"); + mi_option_set(mi_option_purge_delay, before); + + std::vector> v(1000, 7); + check(std::accumulate(v.begin(), v.end(), 0) == 7000, "a vector allocates through mi_stl_allocator"); + check(mi_stl_allocator() == mi_stl_allocator(), "two STL allocators compare equal"); + + mi_stats_t_decl(stats); + check(stats.size == sizeof stats && static_cast(stats.version) == mcpplibs::mwrap::mimalloc::macros::MI_STAT_VERSION, + "mi_stats_t_decl declares a statistics structure with its size and version"); + check(mi_stats_get(&stats), "the process's statistics are read (mcpplibs.mwrap.mimalloc.stats)"); + + std::atomic done{0}; + std::vector threads; + for (int t = 0; t < 4; ++t) + threads.emplace_back([&] { + std::vector mine; + for (int i = 0; i < 10000; ++i) mine.push_back(mi_malloc(16 + i % 512)); + for (void* m : mine) mi_free(m); + done.fetch_add(1); + }); + threads.clear(); + check(done.load() == 4, "four threads allocate and free at once"); + + std::println("-- failures: {} --", failures); + return failures != 0; +} diff --git a/tests/header_mix_test.cpp b/tests/header_mix_test.cpp new file mode 100644 index 0000000..4511087 --- /dev/null +++ b/tests/header_mix_test.cpp @@ -0,0 +1,17 @@ +// The header and the module in one translation unit: the same entities, so a +// block from one spelling is released through the other. +#include +import std; +import mcpplibs.mwrap.mimalloc; + +int main() +{ + void* p = ::mi_malloc(64); // the header's declaration + const bool same = ::mi_usable_size(p) >= 64; + mi_free(p); // the module's + mi_heap_t* h = mi_heap_new(); + const bool heap = h != nullptr; + if (h) mi_heap_delete(h); + std::println("{}", same && heap ? "header and module agree" : "FAIL"); + return same && heap ? 0 : 1; +} diff --git a/tests/macros.cpp b/tests/macros.cpp new file mode 100644 index 0000000..06c2c0b --- /dev/null +++ b/tests/macros.cpp @@ -0,0 +1,48 @@ +// The value macros of mimalloc's headers, as the module's constants: equal to +// the macros of the headers. A macro expands even in a qualified name, so the +// headers' values are taken under other names and the macros undefined before +// the constants are named (docs/usage.md). +#include +#include +#include + +namespace header { +constexpr long long MALLOC_VERSION = MI_MALLOC_VERSION; +constexpr long long SMALL_WSIZE_MAX = MI_SMALL_WSIZE_MAX; +constexpr long long SMALL_SIZE_MAX = MI_SMALL_SIZE_MAX; +constexpr long long STAT_VERSION = MI_STAT_VERSION; +constexpr long long BIN_HUGE = MI_BIN_HUGE; +constexpr long long PROFILE_SAMPLE_DATA_MAX_SIZE = MI_PROFILE_SAMPLE_DATA_MAX_SIZE; +constexpr bool HAS_HEAP_STL_ALLOCATOR = (MI_HAS_HEAP_STL_ALLOCATOR) != 0; +} +#undef MI_MALLOC_VERSION +#undef MI_SMALL_WSIZE_MAX +#undef MI_SMALL_SIZE_MAX +#undef MI_STAT_VERSION +#undef MI_BIN_HUGE +#undef MI_PROFILE_SAMPLE_DATA_MAX_SIZE +#undef MI_HAS_HEAP_STL_ALLOCATOR + +import std; +import mcpplibs.mwrap.mimalloc.macros; + +namespace m = mcpplibs::mwrap::mimalloc::macros; + +int main() +{ + int failures = 0; + auto check = [&](bool ok, std::string_view what) { + std::println("{} {}", ok ? "ok " : "FAIL", what); + if (!ok) ++failures; + }; + check(m::MI_MALLOC_VERSION == header::MALLOC_VERSION && m::MI_MALLOC_VERSION == 30601, "MI_MALLOC_VERSION"); + check(static_cast(m::MI_SMALL_WSIZE_MAX) == header::SMALL_WSIZE_MAX, "MI_SMALL_WSIZE_MAX"); + check(static_cast(m::MI_SMALL_SIZE_MAX) == header::SMALL_SIZE_MAX, "MI_SMALL_SIZE_MAX"); + check(m::MI_STAT_VERSION == header::STAT_VERSION, "MI_STAT_VERSION"); + check(static_cast(m::MI_BIN_HUGE) == header::BIN_HUGE, "MI_BIN_HUGE"); + check(static_cast(m::MI_PROFILE_SAMPLE_DATA_MAX_SIZE) == header::PROFILE_SAMPLE_DATA_MAX_SIZE, + "MI_PROFILE_SAMPLE_DATA_MAX_SIZE"); + check(m::MI_HAS_HEAP_STL_ALLOCATOR == header::HAS_HEAP_STL_ALLOCATOR, "MI_HAS_HEAP_STL_ALLOCATOR"); + std::println("-- failures: {} --", failures); + return failures != 0; +} diff --git a/tools/check_exports.py b/tools/check_exports.py new file mode 100644 index 0000000..faa42b8 --- /dev/null +++ b/tools/check_exports.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Audit the modules against mimalloc's public headers. + +Every function, type and enumerator that , and + declare must be exported by src/*.cppm (`using ::name`), +adapted in mcpplibs::mwrap::mimalloc (an upstream `static inline` function, +which a module cannot export), or listed below with the reason it is not. +Every macro must be a constant of src/macros.cppm, a macro of +include/mwrap/mimalloc/macros.hpp, or listed with its reason. The headers are +read from the release tarball tools/run_conformance.py fetches. +""" +from pathlib import Path +import re +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from run_conformance import fetch # noqa: E402 + +HEADERS = ('mimalloc.h', 'mimalloc-stats.h', 'mimalloc-profile.h') + +# Macros that are part of how the headers are written, not of what they offer. +SYNTAX_MACROS = { + 'MIMALLOC_H', 'MIMALLOC_STATS_H', 'MIMALLOC_PROFILE_H', + 'mi_attr_noexcept', 'mi_attr_noreturn', 'mi_decl_nodiscard', 'mi_decl_export', 'mi_decl_restrict', + 'mi_cdecl', 'mi_attr_malloc', 'mi_attr_alloc_size', 'mi_attr_alloc_size2', 'mi_attr_alloc_align', + 'mi_decl_align', 'mi_decl_thread', 'mi_decl_externc', + 'MI_STAT_FIELDS', 'MI_STAT_COUNT', 'MI_STAT_COUNTER', +} + + +def strip(text: str) -> str: + text = re.sub(r'/\*.*?\*/', ' ', text, flags=re.S) + return re.sub(r'//[^\n]*', '', text) + + +def header_entities(include: Path): + functions, inline, types, enumerators, macros = set(), set(), set(), set(), set() + for name in HEADERS: + text = strip((include / name).read_text(encoding='utf-8')) + for m in re.finditer(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)', text, flags=re.M): + macros.add(m.group(1)) + body = re.sub(r'^\s*#.*$', '', text, flags=re.M) + for m in re.finditer(r'static\s+inline\b[^;{(]*?\b(mi_[a-z0-9_]+)\s*\(', body): + inline.add(m.group(1)) + for m in re.finditer(r'\b(mi_[a-z0-9_]+)\s*\([^;{)]*\)[^;{]*;', body): + if not m.group(1).startswith(('mi_attr', 'mi_decl')): + functions.add(m.group(1)) + for m in re.finditer(r'typedef\b[^;{]*?\b(mi_[a-z0-9_]+(?:_t|_fun))\b[^;{]*;', body, flags=re.S): + types.add(m.group(1)) + for m in re.finditer(r'\}\s*(mi_[a-z0-9_]+_t)\s*;', body): + types.add(m.group(1)) + for m in re.finditer(r'\b(?:struct|enum)\s+(mi_[a-z0-9_]+_[se])\b', body): + types.add(m.group(1)) + for m in re.finditer(r'template\s*<[^>]*>\s*struct\s+(mi_[a-z_]+allocator)\b', body): + types.add(m.group(1)) + for block in re.findall(r'enum\s+mi_[a-z_]+_e\s*\{(.*?)\}', body, flags=re.S): + for item in block.split(','): + item = item.split('=')[0].strip() + if item: + enumerators.add(item) + functions -= inline + return functions, inline, types, enumerators, macros + + +def module_names(root: Path): + exported, adapted = set(), set() + for source in (root / 'src').glob('*.cppm'): + text = strip(source.read_text(encoding='utf-8')) + exported |= set(re.findall(r'using\s+::([A-Za-z_][A-Za-z_0-9]*)\s*;', text)) + for block in re.findall(r'export\s+namespace\s+mcpplibs::mwrap::mimalloc\s*\{(.*?)\}', text, flags=re.S): + adapted |= set(re.findall(r'\b(mi_[a-z0-9_]+)\s*\(', block)) + constants = set(re.findall(r'inline\s+constexpr\s+\w+\s+(MI_[A-Z_]+)\s*=', + (root / 'src/macros.cppm').read_text(encoding='utf-8'))) + kept = set(re.findall(r'#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)', + (root / 'include/mwrap/mimalloc/macros.hpp').read_text(encoding='utf-8'))) + return exported, adapted, constants, kept + + +def main(): + root = Path(__file__).resolve().parents[1] + include = fetch(root) / 'include' + functions, inline, types, enumerators, macros = header_entities(include) + exported, adapted, constants, kept = module_names(root) + problems = [] + for name in sorted(functions | types | enumerators): + if name not in exported: + problems.append(f'not exported: {name}') + for name in sorted(inline): + if name not in adapted: + problems.append(f'not adapted: static inline {name}') + for name in sorted(macros - SYNTAX_MACROS): + if name not in constants and name not in kept and name not in ('MWRAP_MIMALLOC_MACROS_HPP',): + problems.append(f'macro neither mapped nor kept: {name}') + for name in sorted(exported - functions - types - enumerators - {'operator==', 'operator!='}): + problems.append(f'exported but not in the headers: {name}') + for p in problems: + print(p) + print(f'{len(functions)} functions, {len(types)} types, {len(enumerators)} enumerators exported; ' + f'{len(inline)} inline functions adapted; {len(constants)} macros mapped, {len(kept)} kept; ' + f'{len(problems)} problems') + return 1 if problems else 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/check_mwrap_release.py b/tools/check_mwrap_release.py new file mode 100644 index 0000000..9bedddb --- /dev/null +++ b/tools/check_mwrap_release.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Check the immutable mwrap release coordinate and bilingual entry pages.""" +import argparse +import hashlib +import json +from pathlib import Path +import re +import tomllib + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--tag', help='release tag; omitted during PR checks') + args = parser.parse_args() + root = Path(__file__).resolve().parents[1] + package = tomllib.loads((root / 'mcpp.toml').read_text(encoding="utf-8"))['package'] + assert package['namespace'] == 'mcpplibs.mwrap', package + assert re.fullmatch(r'[A-Za-z_][A-Za-z_0-9]*', package['name']), package['name'] + upstream = package['metadata']['mwrap']['upstream-version'] + version = package['version'] + assert version == upstream or re.fullmatch(re.escape(upstream) + r'\.[1-9][0-9]*', version), (version, upstream) + if args.tag: + assert args.tag == 'mwrap-' + version, (args.tag, version) + assert package['license'] == 'Apache-2.0', package['license'] + assert package['authors'] == ['mcpp-community'], package.get('authors') + license_text = (root / 'LICENSE').read_text(encoding='utf-8') + assert 'Apache License' in license_text and 'Version 2.0' in license_text + assert 'mcpp-community' in (root / 'NOTICE').read_text(encoding='utf-8') + documents = json.loads((root / 'upstream/documents.json').read_text(encoding='utf-8'))['documents'] + assert any(Path(d['path']).name.startswith('README') for d in documents) + assert any(Path(d['path']).name.startswith(('LICENSE', 'COPYING')) for d in documents) + deployed = {entry['from'] for entry in tomllib.loads((root / 'mcpp.toml').read_text(encoding='utf-8')).get('runtime', {}).get('deploy', [])} + assert {'LICENSE', 'NOTICE'} <= deployed + for document in documents: + path = Path(document['path']) + assert not path.is_absolute() and '..' not in path.parts and path.parts[0] == 'upstream' + assert hashlib.sha256((root / path).read_bytes()).hexdigest() == document['sha256'], path + assert document['source'] and path.as_posix() in deployed, path + identity = package['namespace'] + '.' + package['name'] + for name, other in [('README.md', 'README.zh.md'), ('README.zh.md', 'README.md')]: + text = (root / name).read_text(encoding="utf-8") + assert len(text.splitlines()) <= 150, name + assert '](' + other + ')' in '\n'.join(text.splitlines()[:12]), name + assert identity in text and version in text, name + assert text.count('```') % 2 == 0, name + print(identity + '@' + version) + + +if __name__ == '__main__': + main() diff --git a/tools/run_conformance.py b/tools/run_conformance.py new file mode 100644 index 0000000..4bc618b --- /dev/null +++ b/tools/run_conformance.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Run mimalloc's own test/test-api.c and test/test-stress.c (of the release) +against compat.mimalloc 3.6.1, the library this wrapper exports. + +The release tarball is downloaded once into target/upstream and checked +against upstream/documents.json. The two tests and their helper header are +copied unchanged into target/conformance/test, and a project there builds them +as C over compat.mimalloc (the tests include and one internal +header, mimalloc/types.h, which a C program cannot import as a module; the +module's own tests are tests/*.cpp). Both run, and both must end with status 0: +test-api reports every case, and test-stress runs `--threads` threads over +`--scale` and `--iterations` (upstream's defaults are 32, 50 and 50; the run +is bounded for a shared runner). +""" +import argparse +import hashlib +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tarfile +import urllib.request + +MANIFEST = '''[package] +name = "mimalloc-conformance" +version = "3.6.1" +description = "mimalloc 3.6.1's test/test-api.c and test/test-stress.c over compat.mimalloc (tools/run_conformance.py)" + +[targets.test-api] +kind = "bin" +main = "test/test-api.c" + +[targets.test-stress] +kind = "bin" +main = "test/test-stress.c" + +[build] +c_standard = "c11" +sources = [] + +[dependencies] +compat.mimalloc = "3.6.1" +''' + + +def fetch(root: Path) -> Path: + pins = json.loads((root / 'upstream/documents.json').read_text(encoding='utf-8')) + cache = root / 'target/upstream' + cache.mkdir(parents=True, exist_ok=True) + archive = cache / 'mimalloc-release.tar.gz' + if not archive.exists() or hashlib.sha256(archive.read_bytes()).hexdigest() != pins['upstream_sha256']: + print('+ fetch', pins['upstream'], flush=True) + with urllib.request.urlopen(pins['upstream'], timeout=120) as response: + archive.write_bytes(response.read()) + digest = hashlib.sha256(archive.read_bytes()).hexdigest() + if digest != pins['upstream_sha256']: + sys.exit(f'upstream archive checksum {digest} != {pins["upstream_sha256"]}') + with tarfile.open(archive) as tar: + top = tar.getmembers()[0].name.split('/')[0] + if not (cache / top).exists(): + tar.extractall(cache, filter='data') + return cache / top + + +def run(command, cwd): + print('+', ' '.join(map(str, command)), flush=True) + return subprocess.run(command, cwd=cwd, check=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--mcpp', default='mcpp') + parser.add_argument('--toolchain') + parser.add_argument('--threads', type=int, default=8) + parser.add_argument('--scale', type=int, default=25) + parser.add_argument('--iterations', type=int, default=5) + args = parser.parse_args() + root = Path(__file__).resolve().parents[1] + upstream = fetch(root) + work = root / 'target/conformance' + shutil.rmtree(work, ignore_errors=True) + (work / 'test').mkdir(parents=True) + for name in ('test-api.c', 'test-stress.c', 'testhelper.h'): + shutil.copyfile(upstream / 'test' / name, work / 'test' / name) + (work / 'mcpp.toml').write_text(MANIFEST, encoding='utf-8') + build = [args.mcpp, 'build', '--release'] + if args.toolchain: + build += ['--toolchain', args.toolchain] + run(build, work) + binaries = {p.stem: p for p in (work / 'target').rglob('*') if p.is_file() and p.stem in ('test-api', 'test-stress') + and p.suffix in ('', '.exe')} + for name in ('test-api', 'test-stress'): + if name not in binaries: + sys.exit(f'{name} did not build') + run([binaries['test-api']], work) + run([binaries['test-stress'], str(args.threads), str(args.scale), str(args.iterations)], work) + print('mimalloc 3.6.1: test-api and test-stress passed over compat.mimalloc', flush=True) + + +if __name__ == '__main__': + main() diff --git a/tools/test_template.py b/tools/test_template.py new file mode 100644 index 0000000..81ea805 --- /dev/null +++ b/tools/test_template.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Render an installed candidate's default mcpp template, then build and run it.""" +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import tomllib + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--mcpp", default="mcpp") + parser.add_argument("--namespace", help="index namespace for legacy manifests without package.namespace") + parser.add_argument("--toolchain", default="llvm@23.1.3") + parser.add_argument("--template", help="named template; omit for the package default") + parser.add_argument("--run-arg", action="append", default=[]) + parser.add_argument("--index", type=Path, help="candidate SDK/wrapper index for unpublished feature dependencies") + args = parser.parse_args() + root = Path(__file__).resolve().parents[1] + metadata = tomllib.loads((root / "mcpp.toml").read_text())["package"] + namespace = metadata.get("namespace", args.namespace) + assert namespace, "Supply the published index namespace for this legacy manifest" + name, version = metadata["name"], metadata["version"] + identity = namespace + "." + name + with tempfile.TemporaryDirectory(prefix=name + "-template-") as directory: + temporary = Path(directory) + home = temporary / "home" + package = home / "registry/data/xpkgs" / (namespace + "-x-" + name) / version / (name + "-" + version) + shutil.copytree(root, package, ignore=shutil.ignore_patterns( + ".git", "target", ".mcpp", ".xlings", ".candidate-index", "__pycache__", "mcpp.lock", "compile_commands.json", "mcpp-2026.*")) + index = home / "indices/mcpplibs/pkgs/mwrap" + index.mkdir(parents=True) + entry = '{url="https://example.invalid/candidate.tar.gz",sha256="fixture"}' + descriptor = ('package={spec="1",namespace=' + json.dumps(namespace) + ',name=' + json.dumps(name) + + ',description="Installed template fixture",type="package",mcpp="mcpp.toml",xpm={') + descriptor += ",".join(platform + "={[" + json.dumps(version) + "]=" + entry + "}" + for platform in ("linux", "macosx", "windows")) + "}}\n" + (index / (identity + ".lua")).write_text(descriptor) + env = dict(os.environ, MCPP_HOME=str(home)) + selector = identity + "@" + version + for command in ([args.mcpp, "new", "--list-templates", selector], + [args.mcpp, "new", "quickstart", "--template", selector + (":" + args.template if args.template else "")]): + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=temporary, env=env, check=True) + project = temporary / "quickstart" + manifest = project / "mcpp.toml" + text = manifest.read_text() + dependency = tomllib.loads(text)["dependencies"][identity] + options = {"version": dependency} if isinstance(dependency, str) else dependency + assert options["version"] == version, options + replacement = '{ path = ' + json.dumps(package.as_posix()) + if options.get("features"): + replacement += ', features = ' + json.dumps(options["features"]) + replacement += ' }' + lines = text.splitlines() + matches = [i for i, line in enumerate(lines) if line.startswith(json.dumps(identity) + " =")] + assert len(matches) == 1, "Expected exactly one qualified dependency from self.name" + lines[matches[0]] = json.dumps(identity) + " = " + replacement + manifest.write_text("\n".join(lines) + "\n") + if args.index: + candidate_index = args.index.resolve() + subprocess.run([os.sys.executable, str(candidate_index / "tests/prepare_mwrap_indices.py")], + cwd=candidate_index, check=True) + with manifest.open("a") as out: + out.write("\n[indices]\n") + for namespace in ("compat", "mcpplibs.mwrap"): + view = candidate_index / "tests/mwrap/.indices" / namespace + out.write(json.dumps(namespace) + ' = { path = ' + json.dumps(view.as_posix()) + ' }\n') + command = [args.mcpp, "build", "--toolchain", args.toolchain, "--strict"] + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=project, check=True) + command = [args.mcpp, "run", "--toolchain", args.toolchain] + if args.run_arg: + command += ["--", *args.run_arg] + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=project, check=True) + + +if __name__ == "__main__": + main() diff --git a/upstream/documents.json b/upstream/documents.json new file mode 100644 index 0000000..6411c05 --- /dev/null +++ b/upstream/documents.json @@ -0,0 +1,17 @@ +{ + "upstream": "https://github.com/microsoft/mimalloc/archive/refs/tags/v3.6.1.tar.gz", + "upstream_sha256": "2672e6a99d4579fb4bb32d9de0d63aa424ce600d31fbfaf06eb74d940d2e07a6", + "pruning": "Nothing of mimalloc is vendored: the library is compat.mimalloc 3.6.1's, from the same tarball, and the typed macros of include/mwrap/mimalloc/macros.hpp are upstream's definitions, written out. Its README and license are retained here. The conformance run builds the release's test/test-api.c and test/test-stress.c against compat.mimalloc, from the tarball downloaded at test time.", + "documents": [ + { + "path": "upstream/mimalloc/README.md", + "source": "https://github.com/microsoft/mimalloc/blob/v3.6.1/readme.md", + "sha256": "626a7860aabfa8b4c03b8365771a81d79abf7f7f3823cc1f40a09a6bcaa21cac" + }, + { + "path": "upstream/mimalloc/LICENSE", + "source": "https://github.com/microsoft/mimalloc/blob/v3.6.1/LICENSE", + "sha256": "82ade5d7d9b029044b5fbbc326207fc47c2d44ee4dd71e8a559c36d728217de9" + } + ] +} diff --git a/upstream/mimalloc/LICENSE b/upstream/mimalloc/LICENSE new file mode 100644 index 0000000..53315eb --- /dev/null +++ b/upstream/mimalloc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2025 Microsoft Corporation, Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/upstream/mimalloc/README.md b/upstream/mimalloc/README.md new file mode 100644 index 0000000..214437c --- /dev/null +++ b/upstream/mimalloc/README.md @@ -0,0 +1,1173 @@ + +
+ v3: + v2: + v1: +   v3: +
+ +# mimalloc + +  + +mimalloc (pronounced "me-malloc") +is a general purpose allocator with excellent [performance](#performance) characteristics. +Initially developed by Daan Leijen for the runtime systems of the +[Koka](https://koka-lang.github.io) and [Lean](https://github.com/leanprover/lean) languages. + +Latest release : `v3.6.1` (2026-10-30) recommended.\ +Latest v2 release: `v2.5.2` (2026-09-12) stable, legacy.\ +Latest v1 release: `v1.15.2` (2026-09-12) legacy. + +mimalloc is a drop-in replacement for `malloc` and can be used in other programs +without code changes, for example, on dynamically linked ELF-based systems (Linux, BSD, etc.) you can use it as: +``` +> LD_PRELOAD=/usr/lib/libmimalloc.so myprogram +``` +It also includes a way to dynamically override the default allocator in [Windows](#override_on_windows). +Notable aspects of the design include: + +- __small and consistent__: the library is about 12k LOC using simple and + consistent data structures. This makes it very suitable + to integrate and adapt in other projects. For runtime systems it + provides hooks for a monotonic _heartbeat_ and deferred freeing (for + bounded worst-case times with reference counting). + Partly due to its simplicity, mimalloc has been ported to many systems (Windows, macOS, + Linux, WASM, various BSD's, Haiku, MUSL, etc) and has excellent support for dynamic overriding. + At the same time, it is an industrial strength allocator that runs (very) large scale + distributed services on thousands of machines with excellent worst case latencies. +- __free list sharding__: instead of one big free list (per size class) we have + many smaller lists per "mimalloc page" which reduces fragmentation and + increases locality -- + things that are allocated close in time get allocated close in memory. + (A mimalloc page contains blocks of one size class and is usually 64KiB on a 64-bit system). +- __free list multi-sharding__: the big idea! Not only do we shard the free list + per mimalloc page, but for each page we have multiple free lists. In particular, there + is one list for thread-local `free` operations, and another one for concurrent `free` + operations. Free-ing from another thread can now be a single CAS without needing + sophisticated coordination between threads. Since there will be + thousands of separate free lists, contention is naturally distributed over the heap, + and the chance of contending on a single location will be low -- this is quite + similar to randomized algorithms like skip lists where adding + a random oracle removes the need for a more complex algorithm. +- __eager page purging__: when a "page" becomes empty (with increased chance + due to free list sharding) the memory is marked to the OS as unused (reset or decommitted) + reducing (real) memory pressure and fragmentation, especially in long running + programs. +- __secure__: _mimalloc_ can be built in secure mode, adding guard pages, + randomized allocation, encrypted free lists, etc. to protect against various + heap vulnerabilities. The performance penalty is usually around 10% on average + over our benchmarks. +- __first-class heaps__: efficiently create and use multiple heaps to allocate across different regions. + A heap can be destroyed at once instead of deallocating each object separately. + New: v3 has true first-class heaps where one can allocate in a heap from any thread. +- __bounded__: it does not suffer from _blowup_ \[1\], has bounded worst-case allocation + times (_wcat_) (upto OS primitives), bounded space overhead (~0.2% meta-data, with low + internal fragmentation), and has no internal points of contention using only atomic operations. +- __fast__: In our benchmarks (see [below](#performance)), + _mimalloc_ outperforms other leading allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc), + and often uses less memory. A nice property is that it does consistently well over a wide range + of benchmarks. There is also good huge OS page support for larger server programs. + +The [documentation](https://microsoft.github.io/mimalloc) gives a full overview of the API. +You 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. + +Enjoy! + +### Versions + +There are three maintained versions of mimalloc. These are mostly equal except for how the OS memory is handled. +New development is mostly on v3, while v1 and v2 are maintained with security and bug fixes. + +- __v3__: recommended: simplifies the lock-free design of previous versions and improves sharing of + memory between threads. On certain large workloads this version may use + (much) less memory. Also supports true first-class heaps (that can allocate from any thread) + and has more efficient heap-walking (for the CPython GC for example). + (release tags: `v3.x`, development branch `dev3`). +- __v2__: stable legacy mimalloc version. Uses thread-local segments to reduce fragmentation. (release tags: `v2.x`, development branch `dev2` and `main`) +- __v1__: legacy version: initial design of mimalloc (release tags: `v1.x`, development branch `dev`). + __Send PR's against this version if possible (and otherwise `dev3`).__ + +### Releases + +* 2026-10-30, `v3.6.1`: (v3) Many small fixes for CPython integration (thank you @DinoV), + Emscripten fixes (#1455, thank you @kleisauke) and reduced memory usage on WASI and Emscripten, + C23 free_sized(_aligned) overrides (#1470), improved fork support (#1456), + fix armv5tej compilation (#1464), fix MSVC overrides for strdup family (#1460), + faster STL allocator trait (#1468), + always use a 2-level pagemap for security and simplicity. + Experimental support for CHERI platforms. Improved Rust bindings (thank you @OscarTHZhang and @Techcable). + Add `-DMI_SECURE=MINIMAL` as improved [security](#secure-mode) with minimal (~1%) performance impact which + puts guard pages around all mimalloc meta-data (but does not encode free lists). + Move all static variables into a [`mi_process_t`](include/mimalloc/heap/proces.h) struct (that is guarded). +* 2026-10-23, `v3.6.0`: (v3) add initial [profiling](#profiling) hooks and default profiler, "always on" + detailed statistics per size-bin, and always-on profiling. Initial rust bindings. Better support + for large OS page sizes, support a quiescent state based reclamation interfacec (for CPython), + better cache line aware allocation, fix heap visitor where some pages might be skipped, improve + fork support, avoid over-allocation for naturally aligned allocations, fix stderr console output + for XBox, fix huge allocation rollback, refactor the source and include files, immediately purge + very large allocations, fix bcrypt dependency on windows, fix heap-destroy bug when a fresh heap + was allocated at the same address (giving a segfault), page_reclaim_on_free is now on by default. +* 2026-09-16, `v3.5.3`: (v3) critical bug fix (where the first new on a thread could + fail on some platforms #1398). Fix bug with `mi_free_size` on overaligned small allocations (#1400). +* 2026-09-12, `v3.5.2`, `v2.5.2`, `v1.15.2`: (v3) Reduced cache contention, improved `mi_malloc_csize`, + improved zero'ing of small blocks, `mi_wmalloc` variants for runtime systems, always enable detailed + statistics. Experimental support for profiling hooks (`mimalloc-profile.h`). +* 2026-09-01: Added a readme section on [getting the best performance](#getting-the-best-performance). +* 2026-09-01, `v3.5.1`, `v2.5.1`, `v1.15.1`: (v3) Yet better performance for `free` calls. + (v3) Added `mi_free_small(_nonnull)` for fast small allocations, and `mi_free_csize(_nonnull)` for inlined constant size free-ing. + (v3) Improved MI_OPT_ARCH options; on arm64, defaults to armv8.3 on Apple and armv8.1 otherwise. + (v3) Default to `-DMI_ALLOW_THP=FULL` on Linux, where we never split transparent huge pages (THP) during purging (#1282). This + may increase rss but can also improve performance. Use `-DMI_ALLOW_THP=OFF` to disable the use of THP. + (v3) Improved cache behavior for small allocations. Improved riscV support. Various small build fixes. +* 2026-08-18, `v1.15.0`, `v2.5.0`, `v3.5.0`: (v3) slightly better performance for `free` + using aligned chunks, cleanup cmake options, require armv8.3 (with MI_OPT_ARCH) for + faster load-acquire, increase retired page count from 1 to 3, faster double free + detection in secure mode, use faster atomics for the arena bitmaps (thank you Alan + Andrade, #1346), faster pagemap lookup. + Other: fix numa sparse node count detection (#1365), add theap stats retrieval, + use __builtin_thread_pointer on riscV (#1363), fix C mode compilation on x86 with + msvc (#1361), improved mingw-ucrt64 support. +* 2026-08-05, `v1.9.15`, `v2.4.5`, `v3.4.5`: Fix compilation with xmalloc (#1353), make the build deterministic (#1355), + mi_zalloc_aligned fix (#763), improve support for mingw (ucrt64), fix fputs fallback on windows (#1354), (v3): use proper + lock backoff for first-class heap deletion, improved riscv64 codegen. +* 2026-08-01, `v1.9.14`, `v2.4.4`, `v3.4.4`: various bug and security fixes through Opus 5 LLM audit (issue #1271, by @Zoxc). + (v3): use pthreads by default on macOS (issue #1333, issue #1327), fix glibc 2.44 crash (issue #1341), fix alignment check for realloc_aligned. (v1,v2,v3): Add initial mingw support, + set `errno` on allocation errors, improved double-free checks and size checks in secure mode, enable + build for universal windows platform and xbox (pr #1340), fix numa node detection when numa nodes are sparse. +* 2026-07-20, `v3.4.3`: revert TLS slots on macOS to 108/109 (issue #1333). +* 2026-07-14, `v1.9.11`, `v2.4.1`, `v3.4.1`: various bug and security fixes through LLM audit (by @Zoxc). + Fix issue with using OS memory instead of arenas for > 4GiB memory usage (v3), fix concurrency bug in concurrent + heap destroy (v3), detect riscV virtual address bits at runtime, add riscV TLS support, reduce spinlock waits (v2), + use TLS slots 126/127 on macOS (v3), and other small fixes. All metadata is now separated from heap objects in v3. +* 2026-04-29, `v1.9.10`, `v2.3.2`, `v3.3.2`: various bug and security fixes through LLM audit (by @Zoxc). + Only increase minimal purge size automatically if allow_thp is set to 2. Enable large OS alignment + on all platforms (fixing OS large pages on Windows). Fix accounting of committed memory on Linux/macOS. + Update MSVC atomics implementation when using C mode. Upstream Emscripten fixes. Proper atomic do-once + implementation. +* 2026-04-20, `v1.9.9`, `v2.3.1`, `v3.3.1`: various bug and security fixes. Special thanks to + @jinpzhanAMD, @res2k, and @GoldJohnKing for their help in improving Windows finalization, and + @Zoxc for his help in finding various issues. + +* [Older release notes](#older-release-notes) + +Special thanks to: + +* Sergiy Kuryata for his contributions on reducing memory commit -- especially on Windows with the Windows thread pool (now implemented in v3). +* [David Carlier](https://devnexen.blogspot.com/) (@devnexen) for his many contributions, and making + mimalloc work better on many less common operating systems, like Haiku, Dragonfly, etc. +* Mary Feofanova (@mary3000), Evgeniy Moiseenko, and Manuel Pöter (@mpoeter) for making mimalloc TSAN checkable, and finding + memory model bugs using the [genMC] model checker. +* Weipeng Liu (@pongba), Zhuowei Li, Junhua Wang, and Jakub Szymanski, for their early support of mimalloc and deployment + at large scale services, leading to many improvements in the mimalloc algorithms for large workloads. +* Jason Gibson (@jasongibson) for exhaustive testing on large scale workloads and server environments, and finding complex bugs + in (early versions of) `mimalloc`. +* 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 + uses mimalloc internally. + + +[genMC]: https://plv.mpi-sws.org/genmc/ + +### Usage + +mimalloc is used in various large scale low-latency services and programs, for example: + + + + + + + + +# Building + +## Windows + +Open `ide/vs2022/mimalloc.sln` in Visual Studio 2022 and build. +The `mimalloc-lib` project builds a static library (in `out/msvc-x64`), while the +`mimalloc-override-dll` project builds a DLL for overriding malloc +in the entire program. + +## Linux, macOS, BSD, etc. + +We use [`cmake`](https://cmake.org) as the build system: + +``` +> mkdir -p out/release +> cd out/release +> cmake ../.. +> make +``` +This builds the library as a shared (dynamic) +library (`.so` or `.dylib`), a static library (`.a`), and +as a single object file (`.o`). + +`> sudo make install` (install the library and header files in `/usr/local/lib` and `/usr/local/include`) + +You can build the debug version which does many internal checks and +maintains detailed statistics as: + +``` +> mkdir -p out/debug +> cd out/debug +> cmake -DCMAKE_BUILD_TYPE=Debug ../.. +> make +``` + +This will name the shared library as `libmimalloc-debug.so`. + +Finally, you can build a _secure_ version that uses guard pages, encrypted free lists, etc., as: + +``` +> mkdir -p out/secure +> cd out/secure +> cmake -DMI_SECURE=ON ../.. +> make +``` + +This will name the shared library as `libmimalloc-secure.so`. +Use `cmake ../.. -LH` to see all the available build options. + +The examples use the default compiler. If you like to use another, use: + +``` +> CC=clang CXX=clang++ cmake ../.. +``` + +## Cmake with Visual Studio + +You can also use cmake on Windows. Open a Visual Studio 2022 development prompt +and invoke `cmake` with the right [generator](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html) +and architecture, like: + +``` +> cmake ..\.. -G "Visual Studio 17 2022" -A x64 -DMI_OVERRIDE=ON +``` + +The cmake build type is specified when actually building, for example: + +``` +> cmake --build . --config=Release +``` + +You can also install the [LLVM toolset](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-170#install-1) +on Windows to build with the `clang-cl` compiler directly: + +``` +> cmake ../.. -G "Visual Studio 17 2022" -T ClangCl +``` + +## Emscripten + +You can build for WebAssembly with [Emscripten](https://emscripten.org) using `emcmake`. +Add `-pthread` to the compiler flags for a multi-threaded build. The tests run under `node`: + +``` +> emcmake cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-pthread +> cmake --build . +> ctest +``` + +When linking with mimalloc, use `-sALLOW_MEMORY_GROWTH` (as mimalloc reserves its arena memory up front; this is +added automatically when linking with the CMake target or `pkg-config`), and `-pthread` if mimalloc was built with it. +Mimalloc reads `MIMALLOC_` options from the program environment; +under `node` you can pass the host environment with `-sNODE_HOST_ENV`, or otherwise set `ENV` in the JavaScript module. + + +## Single Source + +You can also directly build the single `src/static.c` file as part of your project without +needing `cmake` at all. Make sure to also add the mimalloc `include` directory to the include path. + + +# Using the Library + +The preferred usage is including ``, linking with +the shared- or static library, and using the `mi_malloc` API exclusively for allocation. For example, +``` +> gcc -o myprogram -lmimalloc myfile.c +``` + +mimalloc uses only safe OS calls (`mmap` and `VirtualAlloc`) and can co-exist +with other allocators linked to the same program. +If you use `cmake`, you can simply use: +``` +find_package(mimalloc 1.8 REQUIRED) +``` +in your `CMakeLists.txt` to find a locally installed mimalloc. Then use either: +``` +target_link_libraries(myapp PUBLIC mimalloc) +``` +to link with the shared (dynamic) library, or: +``` +target_link_libraries(myapp PUBLIC mimalloc-static) +``` +to link with the static library. See `test\CMakeLists.txt` for an example. + +For best performance in C++ programs, it is also recommended to override the +global `new` and `delete` operators. For convenience, mimalloc provides +[`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. +In C++, mimalloc also provides the `mi_stl_allocator` struct which implements the `std::allocator` +interface. + +You can pass environment variables to print verbose messages (`MIMALLOC_VERBOSE=1`) +and statistics (`MIMALLOC_SHOW_STATS=1`) (in the debug version): +``` +> env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363 + +175451865205073170563711388363 = 374456281610909315237213 * 468551 + +subproc 0 + blocks peak total current block total# + bin S 4: 75.3 KiB 55.2 MiB 0 32 B 1.8 M ok + bin S 6: 31.0 KiB 180.4 KiB 0 48 B 3.8 K ok + bin S 8: 64 B 64 B 0 64 B 1 ok + bin S 9: 160 B 160 B 0 80 B 2 ok + bin S 17: 1.2 KiB 1.2 KiB 0 320 B 4 ok + bin S 21: 640 B 3.1 KiB 0 640 B 5 ok + bin S 33: 5.0 KiB 5.0 KiB 0 5.0 KiB 1 ok + + binned : 84.2 Ki 41.5 Mi 0 ok + huge : 0 0 0 ok + total : 84.2 KiB 41.5 MiB 0 + malloc req: 29.7 MiB + + pages peak total current block total# + touched : 152.8 KiB 152.8 KiB 152.8 KiB + pages : 8 14 0 ok + abandoned : 1 249 0 ok + reclaima : 0 + reclaimf : 249 + reabandon : 0 + waits : 0 + extended : 38 + retire : 35 + searches : 0.7 avg + + arenas peak total current block total# + reserved : 1.0 GiB 1.0 GiB 1.0 GiB + committed : 4.8 MiB 4.8 MiB 4.4 MiB + reset : 0 + purged : 385.5 Ki + arenas : 1 + rollback : 0 + mmaps : 3 + commits : 0 + resets : 1 + purges : 2 + guarded : 0 + heaps : 1 1 1 + + process peak total current block total# + threads : 1 1 1 + numa nodes: 1 + elapsed : 0.553 s + process : user: 0.557 s, system: 0.013 s, faults: 29, peak rss: 2.1 MiB, peak commit: 4.8 MiB +``` + +The above model of using the `mi_` prefixed API is not always possible +though in existing programs that already use the standard malloc interface, +and another option is to override the standard malloc interface +completely and redirect all calls to the _mimalloc_ library instead . + +## Environment Options + +You can set further options either programmatically (using [`mi_option_set`](https://microsoft.github.io/mimalloc/group__options.html)), or via environment variables: + +- `MIMALLOC_SHOW_STATS=1`: show statistics when the program terminates. +- `MIMALLOC_VERBOSE=1`: show verbose messages (including statistics). +- `MIMALLOC_SHOW_ERRORS=1`: show error and warning messages. + +Advanced options: + +- `MIMALLOC_ARENA_EAGER_COMMIT=2`: turns on eager commit for the large arenas (usually 1GiB) from which mimalloc + allocates segments and pages. Set this to 2 (default) to + only enable this on overcommit systems (e.g. Linux). Set this to 1 to enable explicitly on other systems + as well (like Windows or macOS) which may improve performance (as the whole arena is committed at once). + Note that eager commit only increases the commit but not the actual the peak resident set + (rss) so it is generally ok to enable this. +- `MIMALLOC_PURGE_DELAY=N`: the delay in `N` milli-seconds (by default `1000` in v3) after which mimalloc will purge + OS pages that are not in use. This signals to the OS that the underlying physical memory can be reused which + can reduce memory fragmentation especially in long running (server) programs. Setting `N` to `0` purges immediately when + a page becomes unused which can improve memory usage but also decreases performance. + Setting it to `-1` disables purging completely. +- `MIMALLOC_PURGE_DECOMMITS=1`: By default "purging" memory means unused memory is decommitted (`MEM_DECOMMIT` on Windows, + `MADV_DONTNEED` (which decresease rss immediately) on `mmap` systems). Set this to 0 to instead "reset" unused + memory on a purge (`MEM_RESET` on Windows, generally `MADV_FREE` (which does not decrease rss immediately) on `mmap` systems). + Mimalloc generally does not "free" OS memory but only "purges" OS memory, in other words, it will keep virtual + address ranges, but decommits within those ranges (to make the underlying physical memory available to other processes). + +Further options for large workloads and services: + +- `MIMALLOC_ALLOW_THP=[0,1,2]`: When set to `0`, the use of transparent huge pages (THP) is disabled for the process that mimalloc + runs in, and otherwise it is enabled whenever THP is [enabled in the OS][linux-thp] (`[always]` or `[madvise]`). If set to 2 (default on Linux), + mimalloc also sets the `MIMALLOC_MINIMAL_PURGE_SIZE` in v3 to 2MiB to avoid potentially breaking up transparent huge pages when + purging memory which is better for performance (but may increase the rss!). On Android the default setting is 0 to reduce rss. +- `MIMALLOC_USE_NUMA_NODES=N`: pretend there are at most `N` NUMA nodes. If not set, the actual NUMA nodes are detected + at runtime. Setting `N` to 1 may avoid problems in some virtual environments. Also, setting it to a lower number than + the actual NUMA nodes is fine and will only cause threads to potentially allocate more memory across actual NUMA + nodes (but this can happen in any case as NUMA local allocation is always a best effort but not guaranteed). +- `MIMALLOC_ALLOW_LARGE_OS_PAGES=0`: Set to 1 to use large OS pages (2 or 4MiB) when available; for some workloads this can + significantly improve performance. However, large OS pages cannot be purged or shared with other processes so may lead + to increased memory usage in some cases. + Use `MIMALLOC_VERBOSE` to check if the large OS pages are enabled -- usually one needs + to explicitly give permissions for large OS pages (as on [Windows][windows-huge] and [Linux][linux-huge]). However, sometimes + the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that + can have fragmented memory (for that reason, we generally recommend to use `MIMALLOC_RESERVE_HUGE_OS_PAGES` instead whenever possible). +- `MIMALLOC_RESERVE_HUGE_OS_PAGES=N`: where `N` is the number of 1GiB _huge_ OS pages. This reserves the huge pages at + startup and sometimes this can give a large (latency) performance improvement on big workloads. + Usually it is better to not use `MIMALLOC_ALLOW_LARGE_OS_PAGES=1` in combination with this setting. Just like large + OS pages, use with care as reserving + contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at + startup only once). + Note that we usually need to explicitly give permission for huge OS pages (as on [Windows][windows-huge] and [Linux][linux-huge])). + The huge pages are usually allocated evenly among NUMA nodes. + We can use `MIMALLOC_RESERVE_HUGE_OS_PAGES_AT=N` where `N` is the numa node (starting at 0) to allocate all + the huge pages at a specific numa node instead. + +Use caution when using `fork` in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write +for all pages in the original process including the huge OS pages. When any memory is now written in that area, the +OS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in large increments. + +[linux-thp]: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-configuring_transparent_huge_pages +[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 +[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 + +## Secure Mode + +_mimalloc_ can be build in secure mode by using the `-DMI_SECURE=OFF|MINIMAL|ON|FULL` flags in `cmake`. This build mode +enables various mitigations to make mimalloc more robust against exploits. +With `MINIMAL` only security mitigations are added that have minimal impact on performance (usually about 1 or 2%): + +- (v3) All internal mimalloc meta data (including the page map and static data) is surrounded by guard pages + (so a buffer overflow/underflow exploit cannot reach into the allocator's meta data). + However, free lists are not encoded at this level, and a heap block overflow can still corrupt a free list. +- (v3) The start address of OS allocated memory is randomized + (if possible), and mimalloc aborts on corrupted meta data detection. + +With `ON`, performance is impacted a bit more (usually less than 25%), but adds: + +- All free list pointers are + [encoded](include/mimalloc/alloc/page.h#L555) + with per-page keys which is used both to prevent overwrites with a known pointer, as well as to detect heap corruption. +- Double free's are detected (and ignored). +- Padding is added at the end of each heap block to detect heap block overflows (once the block is freed). +- Some use-after-free accesses are detected (if these modify the first word in the block). +- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to + mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc + from the OS are also address randomized. +- (v3) All pointers passed to `mi_free` (and others) are validated, and pointers not originating from a mimalloc allocation + are ignored. + +The `FULL` mode also adds: + +- If using `-DMI_SECURE=FULL` there will also be guard pages at the end of each (64KiB) mimalloc page (thus interleaving + valid block data with inaccessible gaps). This setting is not recommended in general as it is more expensive (and uses + more memory), and can also lead to reaching the maximum VMA limit on Linux systems if the heap gets too large. +- Byte-precise heap block overflow check (once the block is freed). + +As always, evaluate with care as part of an overall security strategy as all of the above are mitigations but not guarantees. + +## Debug Mode + +When _mimalloc_ is built using debug mode, (`-DCMAKE_BUILD_TYPE=Debug`) (or when using `-DMI_DEBUG=ON` explicitly), +various checks are done at runtime to catch development errors. + +- All objects have padding at the end to detect (byte precise) heap block overflows. +- Double free's, and freeing invalid heap pointers are detected. +- Corrupted free-lists and some forms of use-after-free are detected. + +## Guarded Mode + +_mimalloc_ can be build in guarded mode using the `-DMI_GUARDED=ON` flags in `cmake`. +This is `ON` by default when building a debug version of mimalloc. +Guarded mode enables placing OS guard pages behind certain object allocations to catch buffer overflows as they occur. +This can be invaluable to catch buffer-overflow bugs in large programs. However, it also means that any object +allocated with a guard page takes at least 8 KiB memory for the guard page and its alignment. As such, allocating +a guard page for every allocation may be too expensive both in terms of memory, and in terms of performance with +many system calls. Therefore, there are various environment variables (and options) to tune this: + +- `MIMALLOC_GUARDED_SAMPLE_RATE=N`: Set the sample rate to `N` (by default 0). This mode places a guard page + behind a suitable object about every `N` requested bytes (per thread). Since the performance in guarded mode without placing + guard pages is close to release mode, this can be used to enable guard pages even in production to catch latent + buffer overflow bugs (for example, by setting it to `16KiB` or higher where the performance impact will be minimal). + Set the sample rate to `1` to guard every object, and to `0` to place no guard pages at all. + +- `MIMALLOC_GUARDED_SAMPLE_SEED=N`: Start sampling at `N` (by default random). Can be used to reproduce a buffer + overflow if needed. + +- `MIMALLOC_GUARDED_MIN=N`, `MIMALLOC_GUARDED_MAX=N`: Minimal and maximal _rounded_ object sizes for which a guard + page is considered (`0` and `1GiB` respectively). If you suspect a buffer overflow occurs with an object of size + 141, set the minimum and maximum to `148` and the sample rate to `1` to have all of those guarded. + + + +# Overriding Standard Malloc + +Overriding the standard `malloc` (and `new`) can be done either _dynamically_ or _statically_. + + +## Static override + +On Unix like systems, you can statically link with _mimalloc_ to override the standard +malloc interface. The recommended way is to link the final program with the +_mimalloc_ single object file (`mimalloc.o`). We use +an object file instead of a library file as linkers give preference to +that over archives to resolve symbols. To ensure that the standard +malloc interface resolves to the _mimalloc_ library, link it as the first +object file. For example: + +``` +> gcc -o myprogram mimalloc.o myfile1.c ... +``` + +Another way to override statically that works on all platforms, is to +link statically to mimalloc (as shown in the introduction) and include a +header file in each source file that re-defines `malloc` etc. to `mi_malloc`. +This is provided by [`mimalloc-override.h`](include/mimalloc-override.h). This only works +reliably though if all sources are +under your control or otherwise mixing of pointers from different heaps may occur! + +Note: recently we also enabled static overloading on Windows. In that case you need +to link with the static CRT _release_ runtime (`/MT`) and link with the static +`mimalloc(-debug).obj` (to take precendence over the definitions in the CRT library). +The debug CRT (`/MTd`) is not supported. The static override covers the `malloc` family, +`_aligned_*`, `_expand`, `_msize`, `_strdup`, and `_wcsdup`. +Unfortunately, some other CRT functions that return +allocated memory, namely `_dupenv_s`, `_getcwd(NULL,..)`, `_fullpath(NULL,..)`, and `_tempnam`, +still allocate from the CRT heap and cannot be freed with `free`. Use the dynamic override +in that case (or the `mi_` equivalents like `mi_dupenv_s` where available). + + +## Dynamic override + +This is the recommended way to override the standard malloc interface. + +### Dynamic Override on Linux, BSD + +On these ELF-based systems we preload the mimalloc shared +library so all calls to the standard `malloc` interface are +resolved to the _mimalloc_ library. +``` +> env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram +``` + +You can set extra environment variables to check that mimalloc is running, +like: +``` +> env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram +``` +or run with the debug version to get detailed statistics: +``` +> env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram +``` + +### Dynamic Override on MacOS + +On macOS we can also preload the mimalloc shared +library so all calls to the standard `malloc` interface are +resolved to the _mimalloc_ library. +``` +> env DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram +``` + +Note that certain security restrictions may apply when doing this from +the [shell](https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash). + + +### Dynamic Override on Windows + +We use a separate redirection DLL to override mimalloc on Windows +such that we redirect all malloc/free calls that go through the (dynamic) C runtime allocator, +including those from other DLL's or libraries. As it intercepts all allocation calls on a low level, +it can be used on large programs that include other 3rd party components. +There are four requirements to make the overriding work well: + +1. Use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch). + +2. Link your program explicitly with the `mimalloc.dll.lib` export library for the `mimalloc.dll`. + (which must be compiled with `-DMI_OVERRIDE=ON`, which is the default though). + To ensure the `mimalloc.dll` is actually loaded at run-time it is easiest + to insert some call to the mimalloc API in the `main` function, like `mi_version()` + (or use the `/include:mi_version` switch on the linker command, or + similarly, `#pragma comment(linker, "/include:mi_version")` in some source file). + See the `mimalloc-test-override` project for an example on how to use this. + +3. The `mimalloc-redirect.dll` must be put in the same directory as the main + `mimalloc.dll` at runtime (as it is a dependency of that DLL). + The redirection DLL ensures that all calls to the C runtime malloc API get + redirected to mimalloc functions (which reside in `mimalloc.dll`). + +4. Ensure the `mimalloc.dll` comes as early as possible in the import + list of the final executable (so it can intercept all potential allocations). + You can use `minject -l ` to check this if needed. + +For best performance on Windows with C++, it is also recommended to also override +the `new`/`delete` operations (by including [`mimalloc-new-delete.h`](include/mimalloc-new-delete.h) +a single(!) source file in your project). + +The environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic +overriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully +redirected. + +For different platforms than x64, you may need a specific [redirection dll](bin). +Furthermore, we cannot always re-link an executable or ensure `mimalloc.dll` comes +first in the import table. In such cases the [`minject`](bin) tool can be used +to patch the executable's import tables. + + +# Getting the best performance + +Consider the following build options (v3): + +- `-DMI_OPT_ARCH=ON` (by default ON on arm64): enables architecture specific optimizations. + This uses `-march=haswell;-march=avx2` on x64 (available since 2013), `-march=armv8.1` on arm64 (2016), + and `-march=rv64gcb_zacas` on riscV (2023). + Each benefits mimalloc with better bit operations and/or native atomic operations (but also, if your + emulator or hardware does not support these extensions the program will fail with an illegal instruction). + On Apple arm64 the default is `-march=armv8.3` (which is supported on the M1 and better) which includes + efficient load-acquire instructions which mimalloc uses. For riscV the corresponding `zalasr` extension + can be enabled as `-DMI_OPT_ARCH=rv64gcb_zacas_zalasr` when your hardware supports it. + +- `-DMI_ALLOW_THP=OFF`: disabling THP can reduce the rss of certain programs significantly. + +- `-DMI_ALLOW_THP=FULL`: enabling THP can increase performance of certain programs significantly. This setting + still disables THP if the OS THP setting is configured as `[never]`. + +- `-DMI_FREE_IS_CHECKED=ON` (by default ON in secure mode): slightly slower performance but checks validity + of every pointer passed to `mi_free`. Sometimes needed when overriding malloc/free process wide. + Note also that `mi_cfree` (checked free) always checks the validity of pointers passed to it which can + also be used if appropiate without needing to check all pointers process wide. + +In the source code: + +- Use `mi_zalloc` for zero initialized allocations. + +- Use the typed macros `mi_malloc_tp` etc. as these are both more safe and also use `mi_malloc_csize` internally. + +- Use `mi_malloc_csize`/`mi_free_csize` etc. when the allocated or free'd size is a constant as these are inlined + and statically redirect to `mi_malloc_small`/`mi_free_small` when possible. + These are also used by the C++ `mimalloc-new-delete.h` sized overrides. + You can use `mi_free_csize_nonnull` when the pointer is guaranteed to be not `NULL` as well. + +- Use `mi_malloc_small` when the allocation size is guaranteed to be less than `MI_SMALL_SIZE_MAX` (1KiB) for faster allocations. + +- Use `mi_free_small` when the pointer was returned from `mi_malloc_small` (and guaranteed to have a + size of less than `MI_SMALL_SIZE_MAX`). Use `mi_free_small_nonnull` when the pointer is guaranteed to be not `NULL` as well. + +For run-time systems and compilers (like Koka, Lean, Rust (?) etc.), we can do slightly better still. + +- If the allocation size is statically known, use `mi_malloc_csize` and `mi_free_csize` etc. when possible. + (or directly `mi_malloc_small`/`mi_free_small`). + +- If the runtime already carries thread local state, it may be faster to get the default `theap` for each thread + up-front (`mi_theap_get_default()`) and use the bestest `mi_theap_malloc(_small)` etc. passing the theap pointer directly. + This avoids having mimalloc look up the thread local pointer all the time and + skips a theap `NULL` check. Whether this is + faster depends a bit on the OS implementation of thread local variables. + + +# Profiling + +Profiling support is only recently added and may still change. +The profiling support is "always on", even in release builds and only adds overhead once profiling +is actually activated. There is a profiling API that allows the use of custom profiling tools +to hook into mimalloc at runtime, see [`mimalloc-profile.h`](include/mimalloc-profile.h). + +However, mimalloc also has a default built-in profiler that can be used with Google's +[`pprof`](https://github.com/google/pprof) tools. It can be activated using environment variables: + +- `MIMALLOC_PROFILE=`: set a profile basename, this activates the profiler for the process. + This will write _pprof_ compatible `..pb` files. + (If you add the extension `.text` it writes old-style heap profiles as readable text). + You can later analyze the information information using _pprof_ tools, for example, + with a basename of `profile`, we can invoke _pprof_ in the browers as `pprof -http=:8080 ./myprogram profile.*` +- `MIMALLOC_PROFILE_ALLOC_INTERVAL=N`: set the (approximate) number of allocated bytes between saving profile snapshots + of the heap profiler. By default `N` is specified as `1GiB`. +- `MIMALLOC_PROFILE_INUSE_INTERVAL=N`: when the in-use bytes grow by this amount, take a snapshot. By default disabled (`0`). +- `MIMALLOC_PROFILE_TIME_INTERVAL=N`: number of seconds between automatic profile snapshots. By default disabled (`0`). +- `MIMALLOC_PROFILE_SAMPLE_RATE=N`: the sample rate (in bytes) for the heap profiler. Every `N` bytes (approximately, + the profiler uses a Poisson distribution to spread samples) a sample is taken at an allocation call site. By default `512KiB`. + +# Tools + +Generally, we recommend using the standard allocator with memory tracking tools, but mimalloc +can also be build to support the [address sanitizer][asan] or the excellent [Valgrind] tool. +Moreover, it can be build to support Windows event tracing ([ETW]). +This has a small performance overhead but does allow detecting memory leaks and byte-precise +buffer overflows directly on final executables. See also the `test/test-wrong.c` file to test with various tools. + +## Valgrind + +To build with [valgrind] support, use the `MI_TRACK_VALGRIND=ON` cmake option: + +``` +> cmake ../.. -DMI_TRACK_VALGRIND=ON +``` + +This can also be combined with secure mode or debug mode. +You can then run your programs directly under valgrind: + +``` +> valgrind +``` + +If you rely on overriding `malloc`/`free` by mimalloc (instead of using the `mi_malloc`/`mi_free` API directly), +you also need to tell `valgrind` to not intercept those calls itself, and use: + +``` +> MIMALLOC_SHOW_STATS=1 valgrind --soname-synonyms=somalloc=*mimalloc* -- +``` + +By setting the `MIMALLOC_SHOW_STATS` environment variable you can check that mimalloc is indeed +used and not the standard allocator. Even though the [Valgrind option][valgrind-soname] +is called `--soname-synonyms`, this also works when overriding with a static library or object file. +To dynamically override mimalloc using `LD_PRELOAD` together with `valgrind`, use: + +``` +> valgrind --trace-children=yes --soname-synonyms=somalloc=*mimalloc* /usr/bin/env LD_PRELOAD=/usr/lib/libmimalloc.so -- +``` + +See also the `test/test-wrong.c` file to test with `valgrind`. + +Valgrind support is in its initial development -- please report any issues. + +[Valgrind]: https://valgrind.org/ +[valgrind-soname]: https://valgrind.org/docs/manual/manual-core.html#opt.soname-synonyms + +## ASAN + +To build with the address sanitizer, use the `-DMI_TRACK_ASAN=ON` cmake option: + +``` +> cmake ../.. -DMI_TRACK_ASAN=ON +``` + +This can also be combined with secure mode or debug mode. +You can then run your programs as:' + +``` +> ASAN_OPTIONS=verbosity=1 +``` + +When you link a program with an address sanitizer build of mimalloc, you should +generally compile that program too with the address sanitizer enabled. +For example, assuming you build mimalloc in `out/debug`: + +``` +clang -g -o test-wrong -Iinclude test/test-wrong.c out/debug/libmimalloc-asan-debug.a -lpthread -fsanitize=address -fsanitize-recover=address +``` + +Since the address sanitizer redirects the standard allocation functions, on some platforms (macOSX for example) +it is required to compile mimalloc with `-DMI_OVERRIDE=OFF`. +Address sanitizer support is in its initial development -- please report any issues. + +[asan]: https://github.com/google/sanitizers/wiki/AddressSanitizer + +## ETW + +Event tracing for Windows ([ETW]) provides a high performance way to capture all allocations though +mimalloc and analyze them later. To build with ETW support, use the `-DMI_TRACK_ETW=ON` cmake option. + +You can then capture an allocation trace using the Windows performance recorder (WPR), using the +`src/prim/windows/etw-mimalloc.wprp` profile. In an admin prompt, you can use: +``` +> wpr -start src\prim\windows\etw-mimalloc.wprp -filemode +> +> wpr -stop .etl +``` +and then open `.etl` in the Windows Performance Analyzer (WPA), or +use a tool like [TraceControl] that is specialized for analyzing mimalloc traces. + +[ETW]: https://learn.microsoft.com/en-us/windows-hardware/test/wpt/event-tracing-for-windows +[TraceControl]: https://github.com/xinglonghe/TraceControl + + + +# Performance + +Last update: 2021-01-30.\ +(Note 2026-09-01: this is a while ago now, many of these allocators have improved over time as well. YMMV.) + +We tested _mimalloc_ against many other top allocators over a wide +range of benchmarks, ranging from various real world programs to +synthetic benchmarks that see how the allocator behaves under more +extreme circumstances. In our benchmark suite, _mimalloc_ outperforms other leading +allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc), and has a similar memory footprint. A nice property is that it +does consistently well over the wide range of benchmarks. + +General memory allocators are interesting as there exists no algorithm that is +optimal -- for a given allocator one can usually construct a workload +where it does not do so well. The goal is thus to find an allocation +strategy that performs well over a wide range of benchmarks without +suffering from (too much) underperformance in less common situations. + +As always, interpret these results with care since some benchmarks test synthetic +or uncommon situations that may never apply to your workloads. For example, most +allocators do not do well on `xmalloc-testN` but that includes even the best +industrial allocators like _jemalloc_ and _tcmalloc_ that are used in some of +the world's largest systems (like Chrome or FreeBSD). + +Also, the benchmarks here do not measure the behaviour on very large and long-running server workloads, +or worst-case latencies of allocation. Much work has gone into `mimalloc` to work well on such +workloads (for example, to reduce virtual memory fragmentation on long-running services) +but such optimizations are not always reflected in the current benchmark suite. + +We show here only an overview -- for +more specific details and further benchmarks we refer to the +[technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action). +The benchmark suite is automated and available separately +as [mimalloc-bench](https://github.com/daanx/mimalloc-bench). + + +## Benchmark Results on a 16-core AMD 5950x (Zen3) + +Testing on the 16-core AMD 5950x processor at 3.4Ghz (4.9Ghz boost), with +32GiB memory at 3600Mhz, running Ubuntu 20.04 with glibc 2.31 and GCC 9.3.0. + +We measure three versions of _mimalloc_: the main version `mi` (tag:v1.7.0), +the new v2.0 beta version as `xmi` (tag:v2.0.0), and the main version in secure mode as `smi` (tag:v1.7.0). + +The other allocators are +Google's [_tcmalloc_](https://github.com/gperftools/gperftools) (`tc`, tag:gperftools-2.8.1) used in Chrome, +Facebook's [_jemalloc_](https://github.com/jemalloc/jemalloc) (`je`, tag:5.2.1) by Jason Evans used in Firefox and FreeBSD, +the Intel thread building blocks [allocator](https://github.com/intel/tbb) (`tbb`, tag:v2020.3), +[rpmalloc](https://github.com/mjansson/rpmalloc) (`rp`,tag:1.4.1) by Mattias Jansson, +the original scalable [_Hoard_](https://github.com/emeryberger/Hoard) (git:d880f72) allocator by Emery Berger \[1], +the memory compacting [_Mesh_](https://github.com/plasma-umass/Mesh) (git:67ff31a) allocator by +Bobby Powers _et al_ \[8], +and finally the default system allocator (`glibc`, 2.31) (based on _PtMalloc2_). + + + + +Any benchmarks ending in `N` run on all 32 logical cores in parallel. +Results are averaged over 10 runs and reported relative +to mimalloc (where 1.2 means it took 1.2× longer to run). +The legend also contains the _overall relative score_ between the +allocators where 100 points is the maximum if an allocator is fastest on +all benchmarks. + +The single threaded _cfrac_ benchmark by Dave Barrett is an implementation of +continued fraction factorization which uses many small short-lived allocations. +All allocators do well on such common usage, where _mimalloc_ is just a tad +faster than _tcmalloc_ and +_jemalloc_. + +The _leanN_ program is interesting as a large realistic and +concurrent workload of the [Lean](https://github.com/leanprover/lean) +theorem prover compiling its own standard library, and there is a 13% +speedup over _tcmalloc_. This is +quite significant: if Lean spends 20% of its time in the +allocator that means that _mimalloc_ is 1.6× faster than _tcmalloc_ +here. (This is surprising as that is not measured in a pure +allocation benchmark like _alloc-test_. We conjecture that we see this +outsized improvement here because _mimalloc_ has better locality in +the allocation which improves performance for the *other* computations +in a program as well). + +The single threaded _redis_ benchmark again show that most allocators do well on such workloads. + +The _larsonN_ server benchmark by Larson and Krishnan \[2] allocates and frees between threads. They observed this +behavior (which they call _bleeding_) in actual server applications, and the benchmark simulates this. +Here, _mimalloc_ is quite a bit faster than _tcmalloc_ and _jemalloc_ probably due to the object migration between different threads. + +The _mstressN_ workload performs many allocations and re-allocations, +and migrates objects between threads (as in _larsonN_). However, it also +creates and destroys the _N_ worker threads a few times keeping some objects +alive beyond the life time of the allocating thread. We observed this +behavior in many larger server applications. + +The [_rptestN_](https://github.com/mjansson/rpmalloc-benchmark) benchmark +by Mattias Jansson is a allocator test originally designed +for _rpmalloc_, and tries to simulate realistic allocation patterns over +multiple threads. Here the differences between allocators become more apparent. + +The second benchmark set tests specific aspects of the allocators and +shows even more extreme differences between them. + +The _alloc-test_, by +[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 +allocations in various size classes. The test is scaled such that when an +allocator performs almost identically on _alloc-test1_ as _alloc-testN_ it +means that it scales linearly. + +The _sh6bench_ and _sh8bench_ benchmarks are +developed by [MicroQuill](http://www.microquill.com/) as part of SmartHeap. +In _sh6bench_ _mimalloc_ does much +better than the others (more than 2.5× faster than _jemalloc_). +We cannot explain this well but believe it is +caused in part by the "reverse" free-ing pattern in _sh6bench_. +The _sh8bench_ is a variation with object migration +between threads; whereas _tcmalloc_ did well on _sh6bench_, the addition of object migration causes it to be 10× slower than before. + +The _xmalloc-testN_ benchmark by Lever and Boreham \[5] and Christian Eder, simulates an asymmetric workload where +some threads only allocate, and others only free -- they observed this pattern in +larger server applications. Here we see that +the _mimalloc_ technique of having non-contended sharded thread free +lists pays off as it outperforms others by a very large margin. Only _rpmalloc_, _tbb_, and _glibc_ also scale well on this benchmark. + +The _cache-scratch_ benchmark by Emery Berger \[1], and introduced with +the Hoard allocator to test for _passive-false_ sharing of cache lines. +With a single thread they all +perform the same, but when running with multiple threads the potential allocator +induced false sharing of the cache lines can cause large run-time differences. +Crundal \[6] describes in detail why the false cache line sharing occurs in the _tcmalloc_ design, and also discusses how this +can be avoided with some small implementation changes. +Only the _tbb_, _rpmalloc_ and _mesh_ allocators also avoid the +cache line sharing completely, while _Hoard_ and _glibc_ seem to mitigate +the effects. Kukanov and Voss \[7] describe in detail +how the design of _tbb_ avoids the false cache line sharing. + + +## On a 36-core Intel Xeon + +For completeness, here are the results on a big Amazon +[c5.18xlarge](https://aws.amazon.com/ec2/instance-types/#Compute_Optimized) instance +consisting of a 2×18-core Intel Xeon (Cascade Lake) at 3.4GHz (boost 3.5GHz) +with 144GiB ECC memory, running Ubuntu 20.04 with glibc 2.31, GCC 9.3.0, and +Clang 10.0.0. This time, the mimalloc allocators (mi, xmi, and smi) were +compiled with the Clang compiler instead of GCC. +The results are similar to the AMD results but it is interesting to +see the differences in the _larsonN_, _mstressN_, and _xmalloc-testN_ benchmarks. + + + + + +## Peak Working Set + +The following figure shows the peak working set (rss) of the allocators +on the benchmarks (on the c5.18xlarge instance). + + + + +Note that the _xmalloc-testN_ memory usage should be disregarded as it +allocates more the faster the program runs. Similarly, memory usage of +_larsonN_, _mstressN_, _rptestN_ and _sh8bench_ can vary depending on scheduling and +speed. Nevertheless, we hope to improve the memory usage on _mstressN_ +and _rptestN_ (just as _cfrac_, _larsonN_ and _sh8bench_ have a small working set which skews the results). + + + + +# References + +- \[1] Emery D. Berger, Kathryn S. McKinley, Robert D. Blumofe, and Paul R. Wilson. + _Hoard: A Scalable Memory Allocator for Multithreaded Applications_ + the Ninth International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS-IX). Cambridge, MA, November 2000. + [pdf](http://www.cs.utexas.edu/users/mckinley/papers/asplos-2000.pdf) + +- \[2] P. Larson and M. Krishnan. _Memory allocation for long-running server applications_. + 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) + +- \[3] D. Grunwald, B. Zorn, and R. Henderson. + _Improving the cache locality of memory allocation_. In R. Cartwright, editor, + 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) + +- \[4] J. Barnes and P. Hut. _A hierarchical O(n*log(n)) force-calculation algorithm_. Nature, 324:446-449, 1986. + +- \[5] C. Lever, and D. Boreham. _Malloc() Performance in a Multithreaded Linux Environment._ + In USENIX Annual Technical Conference, Freenix Session. San Diego, CA. Jun. 2000. + Available at + +- \[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) + +- \[7] Alexey Kukanov, and Michael J Voss. + _The Foundations for Scalable Multi-Core Software in Intel Threading Building Blocks._ + Intel Technology Journal 11 (4). 2007 + +- \[8] Bobby Powers, David Tench, Emery D. Berger, and Andrew McGregor. + _Mesh: Compacting Memory Management for C/C++_ + In Proceedings of the 40th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI'19), June 2019, pages 333-–346. + + + +# Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + + +# Older Release Notes + +* 2026-04-15, `v1.9.8`, `v2.3.0`, `v3.3.0`: initial support for github (binary) releases, + fix visiting of full pages during collection (performance), + fix THP alignment (performance), fix arm64 cross-compilation on Windows, enable guard pages in debug mode, + always use uncommitted areas between arenas (security), enable static overloading of `malloc` etc. on Windows with the + static CRT (by @Noxybot), fix TLS slot leak on Windows (v3), enable clean DLL load/unload with statically linked + mimalloc (v3), fix race in `mi_heap_destroy` (v3), by default put page meta info separate from allocated + objects (v3,security), fix C++ overrides for emscripten. Various bugs found by DeepTest include: + fix offset for `mi_heap_realloc_aligned`, fix `mi_(w)dupenv_s` buffer size, fix potential overflow in size options, + and error codes for `mi_reallocarr(ay)`. +* 2026-02-03, `v3.2.8` (rc3): Fix thread reinitialize issue on macOS. Fix SIMD codegen bug on older + GCC versions. Extend Windows TLS slot limit from 64 to 1088. Report commit statistics more precise. + Fixes issue in free-page search in arenas. +* 2026-01-15, `v1.9.7`, `v2.2.7`, `v3.2.7` (rc2): Fix zero initializing blocks that were OS allocated.\ + For v3 various bug and performance fixes. Fix Debian 32-bit compilation. +* 2026-01-08, `v1.9.6`, `v2.2.6`, `v3.2.6` (rc1): Important bug fixes. Many improvements to v3 including + true first-class heaps where one can allocate in heap from any thread, and track statistics per heap as well. + Added `MIMALLOC_ALLOW_THP` option. This is by default enabled except on Android. When THP is detected on v3, + mimalloc will set the `MIMALLOC_MINIMAL_PURGE_SIZE` to 2MiB to avoid breaking up potential THP huge pages. + v3 uses faster TLS access on Windows, and has improved performance for `mi_calloc` and aligned allocations. + Fixed rare race condition on older v3, fixed potential buffer overflow in debug statistics, add API for returning + allocated sizes on allocation and free. +* 2025-06-09, `v1.9.4`, `v2.2.4`, `v3.1.4` (beta) : Some important bug fixes, including a case where OS memory + was not always fully released. Improved v3 performance, build on XBox, fix build on Android, support interpose + for older macOS versions, use MADV_FREE_REUSABLE on macOS, always check commit success, better support for Windows + fixed TLS offset, etc. +* 2025-03-28, `v1.9.3`, `v2.2.3`, `v3.0.3` (beta) : Various small bug and build fixes, including: + fix arm32 pre v7 builds, fix mingw build, get runtime statistics, improve statistic commit counts, + fix execution on non BMI1 x64 systems. +* 2025-03-06, `v1.9.2`, `v2.2.2`, `v3.0.2-beta`: Various small bug and build fixes. + Add `mi_options_print`, `mi_arenas_print`, and the experimental `mi_stat_get` and `mi_stat_get_json`. + Add `mi_thread_set_in_threadpool` and `mi_heap_set_numa_affinity` (v3 only). Add vcpkg portfile. + Upgrade mimalloc-redirect to v1.3.2. `MI_OPT_ARCH` is off by default now but still assumes armv8.1-a on arm64 + for fast atomic operations. Add QNX support. +* 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 + guard pages behind objects to catch buffer overflows as they occur. + Many small fixes: build on Windows arm64, cygwin, riscV, and dragonfly; fix Windows static library initialization to account for + thread local destructors (in Rust/C++); macOS tag change; macOS TLS slot fix; improve stats; + consistent `mimalloc.dll` on Windows (instead of `mimalloc-override.dll`); fix mimalloc-redirect on Win11 H2; + add 0-byte to canary; upstream CPython fixes; reduce .bss size; allow fixed TLS slot on Windows for improved performance. + +* 2024-05-21, `v1.8.7`, `v2.1.7`: Fix build issues on less common platforms. Started upstreaming patches + from the CPython [integration](https://github.com/python/cpython/issues/113141#issuecomment-2119255217). Upstream `vcpkg` patches. +* 2024-05-13, `v1.8.6`, `v2.1.6`: Fix build errors on various (older) platforms. Refactored aligned allocation. +* 2024-04-22, `v1.8.4`, `v2.1.4`: Fixes various bugs and build issues. Add `MI_LIBC_MUSL` cmake flag for musl builds. + Free-ing code is refactored into a separate module (`free.c`). Mimalloc page info is simplified with the block size + directly available (and new `block_size_shift` to improve aligned block free-ing). + New approach to collection of abandoned segments: When + a thread terminates the segments it owns are abandoned (containing still live objects) and these can be + reclaimed by other threads. We no longer use a list of abandoned segments but this is now done using bitmaps in arena's + which is more concurrent (and more aggressive). Abandoned memory can now also be reclaimed if a thread frees an object in + an abandoned page (which can be disabled using `mi_option_abandoned_reclaim_on_free`). The option `mi_option_max_segment_reclaim` + gives a maximum percentage of abandoned segments that can be reclaimed per try (=10%). + +* 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 + by removing regions and segment-cache's and only use arenas with improved memory purging -- this may improve memory + usage as well for larger services. Renamed options for consistency. Improved Valgrind and ASAN checking. + +* 2023-04-03, `v1.8.1`, `v2.1.1`: Fixes build issues on some platforms. + +* 2023-03-29, `v1.8.0`, `v2.1.0`: Improved support dynamic overriding on Windows 11. Improved tracing precision + with [asan](#asan) and [Valgrind](#valgrind), and added Windows event tracing [ETW](#ETW) (contributed by Xinglong He). Created an OS + 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. + +* 2022-12-23, `v1.7.9`, `v2.0.9`: Supports building with [asan](#asan) and improved [Valgrind](#valgrind) support. + Support arbitrary large alignments (in particular for `std::pmr` pools). + Added C++ STL allocators attached to a specific heap (thanks @vmarkovtsev). + Heap walks now visit all object (including huge objects). Support Windows nano server containers (by Johannes Schindelin,@dscho). + Various small bug fixes. + +* 2022-11-03, `v1.7.7`, `v2.0.7`: Initial support for [Valgrind](#valgrind) for leak testing and heap block overflow + detection. Initial + 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`, . + +* 2022-04-14, `v1.7.6`, `v2.0.6`: fix fallback path for aligned OS allocation on Windows, improve Windows aligned allocation + even when compiling with older SDK's, fix dynamic overriding on macOS Monterey, fix MSVC C++ dynamic overriding, fix + warnings under Clang 14, improve performance if many OS threads are created and destroyed, fix statistics for large object + allocations, using MIMALLOC_VERBOSE=1 has no maximum on the number of error messages, various small fixes. + +* 2022-02-14, `v1.7.5`, `v2.0.5` (alpha): fix malloc override on + Windows 11, fix compilation with musl, potentially reduced + committed memory, add `bin/minject` for Windows, + improved wasm support, faster aligned allocation, + various small fixes. + +* 2021-11-14, `v1.7.3`, `v2.0.3` (beta): improved WASM support, improved macOS support and performance (including + M1), improved performance for v2 for large objects, Python integration improvements, more standard + installation directories, various small fixes. +* 2021-06-17, `v1.7.2`, `v2.0.2` (beta): support M1, better installation layout on Linux, fix + thread_id on Android, prefer 2-6TiB area for aligned allocation to work better on pre-windows 8, various small fixes. +* 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). +* 2021-01-31, `v2.0.0`: beta release 2.0: new slice algorithm for managing internal mimalloc pages. +* 2021-01-31, `v1.7.0`: stable release 1.7: support explicit user provided memory regions, more precise statistics, + improve macOS overriding, initial support for Apple M1, improved DragonFly support, faster memcpy on Windows, various small fixes. + +* 2020-09-24, `v1.6.7`: stable release 1.6: using standard C atomics, passing tsan testing, improved + handling of failing to commit on Windows, add [`mi_process_info`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc.h#L156) api call. +* 2020-08-06, `v1.6.4`: stable release 1.6: improved error recovery in low-memory situations, + support for IllumOS and Haiku, NUMA support for Vista/XP, improved NUMA detection for AMD Ryzen, ubsan support. +* 2020-05-05, `v1.6.3`: stable release 1.6: improved behavior in out-of-memory situations, improved malloc zones on macOS, + build PIC static libraries by default, add option to abort on out-of-memory, line buffered statistics. +* 2020-04-20, `v1.6.2`: stable release 1.6: fix compilation on Android, MingW, Raspberry, and Conda, + stability fix for Windows 7, fix multiple mimalloc instances in one executable, fix `strnlen` overload, + fix aligned debug padding. +* 2020-02-17, `v1.6.1`: stable release 1.6: minor updates (build with clang-cl, fix alignment issue for small objects). +* 2020-02-09, `v1.6.0`: stable release 1.6: fixed potential memory leak, improved overriding + and thread local support on FreeBSD, NetBSD, DragonFly, and macOSX. New byte-precise + heap block overflow detection in debug mode (besides the double-free detection and free-list + corruption detection). Add `nodiscard` attribute to most allocation functions. + Enable `MIMALLOC_PAGE_RESET` by default. New reclamation strategy for abandoned heap pages + for better memory footprint. +* 2020-02-09, `v1.5.0`: stable release 1.5: improved free performance, small bug fixes. +* 2020-01-22, `v1.4.0`: stable release 1.4: improved performance for delayed OS page reset, +more eager concurrent free, addition of STL allocator, fixed potential memory leak. +* 2020-01-15, `v1.3.0`: stable release 1.3: bug fixes, improved randomness and [stronger +free list encoding](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) in secure mode. + +* 2019-12-22, `v1.2.2`: stable release 1.2: minor updates. +* 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. +* 2019-10-07, `v1.1.0`: stable release 1.1. +* 2019-09-01, `v1.0.8`: pre-release 8: more robust windows dynamic overriding, initial huge page support. +* 2019-08-10, `v1.0.6`: pre-release 6: various performance improvements.