diff --git a/.agents/docs/2026-11-02-add-mimalloc-mwrap-plan.md b/.agents/docs/2026-11-02-add-mimalloc-mwrap-plan.md new file mode 100644 index 00000000..651b15a4 --- /dev/null +++ b/.agents/docs/2026-11-02-add-mimalloc-mwrap-plan.md @@ -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 `` + by `export using`, in the global namespace where upstream declares them; it re-exports the three below. +- `mcpplibs.mwrap.mimalloc.stats` (``) and `mcpplibs.mwrap.mimalloc.profile` (``); + 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 ``. + +## 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`. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7cf7a4ca..7fb176a0 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a30c43c..516e3e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ ### Added +- **mcpplibs.mwrap.mimalloc 3.6.1(新包)。** mimalloc-m:以 C++23 模块(`import mcpplibs.mwrap.mimalloc;`)使用 compat.mimalloc + 3.6.1。`` 的函数、堆、arena、子进程、选项与 STL 分配器以上游的全局名导出;`.stats`、`.profile` 两个子模块对应 + ``、``;上游的 `static inline` 辅助函数以同名适配在 `mcpplibs::mwrap::mimalloc`;值宏在 + `.macros` 中为常量,带类型参数的宏与 `mi_stats_t_decl` 留在 ``。消费者测试 + `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 为 diff --git a/mcpp.toml b/mcpp.toml index 17f0bc9c..a1895bf0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -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", diff --git a/pkgs/mwrap/mimalloc.lua b/pkgs/mwrap/mimalloc.lua new file mode 100644 index 00000000..e3863415 --- /dev/null +++ b/pkgs/mwrap/mimalloc.lua @@ -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", + }, + }, + }, +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index ce4fd6bc..9cafedee 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -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, diff --git a/tests/mwrap/mimalloc-heap/mcpp.toml b/tests/mwrap/mimalloc-heap/mcpp.toml new file mode 100644 index 00000000..7009a224 --- /dev/null +++ b/tests/mwrap/mimalloc-heap/mcpp.toml @@ -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" diff --git a/tests/mwrap/mimalloc-heap/tests/behavior.cpp b/tests/mwrap/mimalloc-heap/tests/behavior.cpp new file mode 100644 index 00000000..3d2053bc --- /dev/null +++ b/tests/mwrap/mimalloc-heap/tests/behavior.cpp @@ -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(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(a) % align == 0, std::format("a block aligned to {}", align)); + mi_free(a); + } + + mi_heap_t* heap = mi_heap_new(); + std::vector 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(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> 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 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; +} diff --git a/tests/mwrap/mimalloc-heap/tests/side_header.cpp b/tests/mwrap/mimalloc-heap/tests/side_header.cpp new file mode 100644 index 00000000..7694f6e3 --- /dev/null +++ b/tests/mwrap/mimalloc-heap/tests/side_header.cpp @@ -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 + +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(stats.version) != mcpplibs::mwrap::mimalloc::macros::MI_STAT_VERSION) return 3; + return sum == 42 ? 0 : 4; +}