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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .agents/docs/2026-11-02-add-mimalloc-mwrap-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Add mcpplibs.mwrap.mimalloc (mimalloc-m 3.6.1)

## Form

- Third-party upstream [microsoft/mimalloc](https://github.com/microsoft/mimalloc), release v3.6.1, MIT. The library is
`compat.mimalloc` 3.6.1 of this index (`pkgs/c/compat.mimalloc.lua`), the archive openkal-musl 0.26.0 embeds.
- Form A external mwrap module repository [mcpplibs/mimalloc-m](https://github.com/mcpplibs/mimalloc-m), package
`mcpplibs.mwrap.mimalloc`, version 3.6.1 (first release of the identity), tag `mwrap-3.6.1` on the merged main commit
(`0582ff7`); release asset `mimalloc-m-3.6.1.tar.gz` (`62ae5121…9abe`), CN mirror `gitcode.com/mcpp-res/mwrap-mimalloc`
with identical bytes.
- Implementation ownership: the library is compat.mimalloc's. The wrapper vendors no upstream code; it retains upstream's
README.md and LICENSE under `upstream/mimalloc/` with checksums in `upstream/documents.json`, and deploys them with its
own LICENSE/NOTICE (`runtime.deploy`). The adaptation a C library needs to embed mimalloc is in that C library
(openkal-musl `port/src/mimalloc`), not here (mcpp design 2026-11-02 part 3 §7.1).

## Modules

- `mcpplibs.mwrap.mimalloc`: the functions, heaps, arenas, sub-processes, options and STL allocators of `<mimalloc.h>`
by `export using`, in the global namespace where upstream declares them; it re-exports the three below.
- `mcpplibs.mwrap.mimalloc.stats` (`<mimalloc-stats.h>`) and `mcpplibs.mwrap.mimalloc.profile` (`<mimalloc-profile.h>`);
upstream's `static inline` helpers (`mi_malloc_csize`, `mi_zalloc_csize`, `mi_free_csize`, `mi_stats_init`,
`mi_stats_header_init`) adapted under their names in `mcpplibs::mwrap::mimalloc`.
- `mcpplibs.mwrap.mimalloc.macros`: the value macros (`MI_MALLOC_VERSION`, `MI_STAT_VERSION`, `MI_SMALL_SIZE_MAX` …)
as constants. The macros that take a type (`mi_malloc_tp` and its eleven companions) and `mi_stats_t_decl` stay
macros, in `<mwrap/mimalloc/macros.hpp>`.

## Verification

- Wrapper CI (Linux x86_64 GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM): the module tests, the export check
(`tools/check_exports.py`) and upstream's `test-api` and `test-stress` over the same library; the default template in
a workflow of its own.
- `tests/mwrap/mimalloc-heap`: the real release archive through the candidate index; a block that grows and keeps its
bytes, alignments to 64 KiB, a private heap visited and destroyed, a container over `mi_stl_allocator`, an option,
blocks freed by another thread, the statistics; and the side header's typed macros beside the import.
- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.mimalloc@3.6.1 --deploy`: the unmodified
default `allocate` template from the real archive, then a relocated bundle whose `licenses/mimalloc/` holds the
wrapper's LICENSE/NOTICE and mimalloc's README and LICENSE byte for byte.

## Motivation

mcpp design 2026-11-02 part 3 (§7, §7.1): openkal-musl 0.26 embeds mimalloc as the allocator of the ok image, and a
program that wants mimalloc's own interface (heaps, options, statistics) imports it through this module, over the same
`compat.mimalloc`.
5 changes: 5 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1666,6 +1666,11 @@ jobs:
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.box2d@3.2.0 --deploy

- name: Installed mimalloc wrapper archive, actual default allocation template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' mimalloc-heap ') && matrix.toolchain == 'default'
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.mimalloc@3.6.1 --deploy

- name: Installed libwebp wrapper archive, actual default image template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' libwebp-image ') && matrix.toolchain == 'default'
shell: bash
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@

### Added

- **mcpplibs.mwrap.mimalloc 3.6.1(新包)。** mimalloc-m:以 C++23 模块(`import mcpplibs.mwrap.mimalloc;`)使用 compat.mimalloc
3.6.1。`<mimalloc.h>` 的函数、堆、arena、子进程、选项与 STL 分配器以上游的全局名导出;`.stats`、`.profile` 两个子模块对应
`<mimalloc-stats.h>`、`<mimalloc-profile.h>`;上游的 `static inline` 辅助函数以同名适配在 `mcpplibs::mwrap::mimalloc`;值宏在
`.macros` 中为常量,带类型参数的宏与 `mi_stats_t_decl` 留在 `<mwrap/mimalloc/macros.hpp>`。消费者测试
`tests/mwrap/mimalloc-heap` 与已安装模板检查(`allocate` 模板与重定位后的许可文件)。GLOBAL 为 GitHub 发布资产,CN 为
GitCode `mcpp-res/mwrap-mimalloc` 镜像,sha256 两处一致。
- **openkal-ld/run/tools/bind 0.3.0。** openkal 0.21 的一轮:okld 把不可写、文件与内存大小相同、与文件偏移模 64 KiB 同余且独占所在
粒度的段从映像文件映射(`okld_host::map`,即 `kal_exec_map_part`),其余段照旧复制;每个重定位须写入可写段,否则拒绝映像;
okrun 在程序运行期间保持映像文件打开。测试成员 `openkal-tools`、`openkal-bind` 随之移动。GLOBAL 为 GitHub 标签归档,CN 为
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ members = [
"tests/mwrap/opus-ogg",
"tests/mwrap/libvpx-webm",
"tests/mwrap/box2d-physics",
"tests/mwrap/mimalloc-heap",
"tests/mwrap/miniaudio-engine",
"tests/mwrap/freetype-render",
"tests/mwrap/harfbuzz-shape",
Expand Down
50 changes: 50 additions & 0 deletions pkgs/mwrap/mimalloc.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- Immutable upstream-aligned mwrap source release (mimalloc-m mwrap-3.6.1);
-- GLOBAL and CN bytes are identical.
--
-- The wrapper is Apache-2.0; the library it links, compat.mimalloc (mimalloc
-- 3.6.1), is MIT, linked statically: the package deploys mimalloc's README and
-- LICENSE beside programs (runtime.deploy).
package = {
spec = "1",
namespace = "mcpplibs.mwrap",
name = "mimalloc",
description = "mimalloc-m: mimalloc 3.6.1 (Microsoft's general-purpose allocator) as C++23 modules: its heaps, options, statistics, profiler and STL allocators",
licenses = { "Apache-2.0" },
repo = "https://github.com/mcpplibs/mimalloc-m",
type = "package",
extensions = {
autoupdate = {
tag = "mwrap-${version}",
url = "https://github.com/mcpplibs/mimalloc-m/releases/download/${tag}/mimalloc-m-${version}.tar.gz",
},
},
xpm = {
linux = {
["3.6.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/mimalloc-m/releases/download/mwrap-3.6.1/mimalloc-m-3.6.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-mimalloc/releases/download/3.6.1/mimalloc-m-3.6.1.tar.gz",
},
sha256 = "62ae512171fcbff73f557b1b1af505850d6e87a482a63cd956a409a1fe569abe",
},
},
macosx = {
["3.6.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/mimalloc-m/releases/download/mwrap-3.6.1/mimalloc-m-3.6.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-mimalloc/releases/download/3.6.1/mimalloc-m-3.6.1.tar.gz",
},
sha256 = "62ae512171fcbff73f557b1b1af505850d6e87a482a63cd956a409a1fe569abe",
},
},
windows = {
["3.6.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/mimalloc-m/releases/download/mwrap-3.6.1/mimalloc-m-3.6.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-mimalloc/releases/download/3.6.1/mimalloc-m-3.6.1.tar.gz",
},
sha256 = "62ae512171fcbff73f557b1b1af505850d6e87a482a63cd956a409a1fe569abe",
},
},
},
}
20 changes: 20 additions & 0 deletions tests/mwrap/check_installed_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,26 @@ def main():
notice = (roots[0] / "NOTICE").read_bytes()
assert b"mcpp-community" in notice and b"Erin Catto" in notice
print(f"Verified {len(declared)} byte-identical wrapper/Box2D license files", flush=True)
elif identity == "mcpplibs.mwrap.mimalloc":
# mimalloc is MIT and linked statically: its LICENSE and README
# travel with the program beside the wrapper's LICENSE/NOTICE.
sources = [path for store in (home / "xpkgs", home / "registry/data/xpkgs")
for path in store.glob(f"mcpplibs.mwrap-x-mimalloc/{version}/*/src/mimalloc.cppm")]
assert len(sources) == 1, sources
source = sources[0].parent.parent
declared = tomllib.loads((source / "mcpp.toml").read_text(encoding="utf-8"))["runtime"]["deploy"]
roots = list(relocated.rglob("licenses/mimalloc"))
assert len(roots) == 1, f"Missing mimalloc runtime license tree: {roots}"
for entry in declared:
notice = source / entry["from"]
copied = roots[0].parent.parent / entry["to"] / notice.name
assert copied.is_file() and copied.read_bytes() == notice.read_bytes(), copied
assert b"MIT License" in (roots[0] / "upstream/mimalloc/LICENSE").read_bytes()
assert b"Microsoft Corporation" in (roots[0] / "upstream/mimalloc/LICENSE").read_bytes()
assert b"Apache License" in (roots[0] / "LICENSE").read_bytes()
notice = (roots[0] / "NOTICE").read_bytes()
assert b"mcpp-community" in notice and b"Daan Leijen" in notice
print(f"Verified {len(declared)} byte-identical wrapper/mimalloc license files", flush=True)
elif identity == "mcpplibs.mwrap.libwebp":
# libwebp (BSD-3-Clause with Google's patent grant) is linked
# statically: the wrapper's LICENSE/NOTICE and libwebp's README,
Expand Down
11 changes: 11 additions & 0 deletions tests/mwrap/mimalloc-heap/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "mimalloc-heap"
version = "3.6.1"
description = "Consume the immutable mimalloc wrapper: private heaps visited and destroyed, aligned blocks, an STL container over mimalloc, options and statistics, threads that free each other's blocks, and the side header beside the import"

[indices]
# Use the complete candidate built-in index prepared by tests/run_members.sh.
# An explicit empty table overrides the legacy workspace redirect.

[dependencies]
"mcpplibs.mwrap.mimalloc" = "3.6.1"
71 changes: 71 additions & 0 deletions tests/mwrap/mimalloc-heap/tests/behavior.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// The installed wrapper, as a consumer reaches it: each check names what the
// allocator promises.
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;
}
}

int main()
{
namespace m = mcpplibs::mwrap::mimalloc::macros;
check(mi_version() == m::MI_MALLOC_VERSION && mi_version() == 30601, "the library is mimalloc 3.6.1");

void* p = mi_malloc(1000);
std::memset(p, 0x5a, 1000);
p = mi_realloc(p, 1 << 20);
check(p && static_cast<unsigned char*>(p)[999] == 0x5a && mi_usable_size(p) >= (1u << 20),
"a block grows and keeps its bytes");
mi_free(p);

for (std::size_t align : {16u, 256u, 4096u, 65536u}) {
void* a = mi_malloc_aligned(align + 3, align);
check(a && reinterpret_cast<std::uintptr_t>(a) % align == 0, std::format("a block aligned to {}", align));
mi_free(a);
}

mi_heap_t* heap = mi_heap_new();
std::vector<void*> blocks;
for (int i = 0; i < 2000; ++i) blocks.push_back(mi_heap_malloc(heap, 8 + i % 700));
std::size_t seen = 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<std::size_t*>(arg);
return true;
}, &seen);
check(seen == blocks.size() && mi_heap_contains(heap, blocks.back()),
"a private heap holds and visits every block it handed out");
mi_heap_destroy(heap);

std::vector<std::string, mi_stl_allocator<std::string>> names;
for (int i = 0; i < 5000; ++i) names.push_back(std::format("module{}.cppm", i));
check(names.size() == 5000 && names[4999] == "module4999.cppm", "a container's elements live in mimalloc");

const long before = mi_option_get(mi_option_purge_delay);
mi_option_set(mi_option_purge_delay, before + 5);
check(mi_option_get(mi_option_purge_delay) == before + 5, "an option is set and read back");
mi_option_set(mi_option_purge_delay, before);

// Blocks freed by a thread other than the one that allocated them, and the
// memory taken again afterwards.
std::vector<void*> handed(20000);
std::thread([&] { for (auto& b : handed) b = mi_malloc(48); }).join();
const bool all = std::ranges::all_of(handed, [](void* b) { return b != nullptr; });
std::thread([&] { for (auto* b : handed) mi_free(b); }).join();
mi_collect(true);
void* again = mi_malloc(48);
check(all && again != nullptr, "blocks allocated in one thread are freed in another");
mi_free(again);

mi_stats_t stats;
mcpplibs::mwrap::mimalloc::mi_stats_init(&stats);
check(mi_stats_get(&stats) && stats.size == sizeof stats, "the process's statistics are read");

std::println("-- failures: {} --", failures);
return failures == 0 ? 0 : 1;
}
22 changes: 22 additions & 0 deletions tests/mwrap/mimalloc-heap/tests/side_header.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// The side header from the installed package, beside the import: the typed
// macros with the module's entities, one allocator under both spellings.
#include <mwrap/mimalloc/macros.hpp>

import std;
import mcpplibs.mwrap.mimalloc;

struct pair { long a, b; };

int main() {
pair* p = mi_malloc_tp(pair);
if (!p) return 1;
p->a = 20; p->b = 22;
const long sum = p->a + p->b;
mi_free_tp(pair, p);
pair* many = mi_mallocn_tp(pair, 64);
if (!many || mi_usable_size(many) < 64 * sizeof(pair)) return 2;
mi_free(many);
mi_stats_t_decl(stats);
if (static_cast<int>(stats.version) != mcpplibs::mwrap::mimalloc::macros::MI_STAT_VERSION) return 3;
return sum == 42 ? 0 : 4;
}
Loading