Merge origin/main into fix/2537-go-net-http-servemux-patterns-with-a-met

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-10-29 03:33:16 +01:00
commit fec0b87b4c
76 files changed
+8572 -4472

No files matched your search

+25 -7
View File
@@ -45,8 +45,9 @@ endif
# -Wdate-time: with -Werror, any use of __DATE__/__TIME__/__TIMESTAMP__ fails the
# build. Build-time entropy is how one identical source tree produced a new hash
# on every compile (see the local patch in vendored/mimalloc/src/options.c) —
# once removed, it must not creep back through a new call site.
# on every compile (mimalloc's version banner, patched locally until upstream
# dropped the clock in v3.4.5; see vendored/mimalloc/PATCHES.md) — once
# removed, it must not creep back through a new call site.
CFLAGS_COMMON = -std=c11 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall -Wextra -Werror \
-Wno-unused-parameter -Wno-sign-compare -Wdate-time \
$(GCC_ONLY_FLAGS) \
@@ -182,8 +183,9 @@ endif
ifeq ($(IS_MINGW),yes)
# --no-insert-timestamp: the PE header otherwise carries the link wall clock, so
# two Windows builds of identical source are never byte-identical and every
# release is a brand-new file to a reputation system. Same motivation as the
# mimalloc __DATE__ patch; ELF and Mach-O have no equivalent field to clear.
# release is a brand-new file to a reputation system. Same motivation as
# dropping mimalloc's __DATE__ banner; ELF and Mach-O have no equivalent field
# to clear.
WIN32_LIBS := -lws2_32 -lpsapi -lshell32 -ladvapi32 -lbcrypt -Wl,--allow-multiple-definition -Wl,--stack,8388608 -Wl,--no-insert-timestamp -static
endif
@@ -644,6 +646,18 @@ ifneq ($(filter yes,$(IS_MINGW) $(IS_LINUX)),)
MIMALLOC_OVERRIDE_DEFINE := -DMI_MALLOC_OVERRIDE=1
GLOBAL_OVERRIDE_DEFINE := -DCBM_MEM_GLOBAL_OVERRIDE=1
endif
# MI_MINGW_UCRT64: since 3.5.3, mimalloc treats a MinGW build as having a
# process-attach hook only when this is defined (src/prim/windows/prim.c);
# upstream's CMake sets it, this Makefile must too. Without it src/prim/prim.c
# also compiles its constructor, so _mi_auto_process_init runs twice (that
# constructor plus the CRT TLS callback) and the second options flush writes a
# stray newline to stderr before main: a blank first line on every run, which
# failed smoke-win's one-line --version check. Every MinGW toolchain we build
# with (MSYS2 CLANG64/CLANGARM64) is UCRT. 3.3.2 had no such condition.
MIMALLOC_WIN_DEFINE :=
ifeq ($(IS_MINGW),yes)
MIMALLOC_WIN_DEFINE := -DMI_MINGW_UCRT64=1
endif
# MI_DEBUG=0: the production allocator is mimalloc's RELEASE build. Its header
# defaults to MI_DEBUG=2 unless NDEBUG is defined, and nothing here defined it,
# so every shipped binary up to v0.11.0 ran mimalloc's debug build: internal
@@ -653,15 +667,19 @@ endif
# work inside the allocator (2026-09-17). Committed-bytes accounting, which the
# memory budget reads, is kept in every build (subproc stats, not MI_STAT).
# The TEST build below keeps mimalloc's debug checks.
# MI_PROFILE=0: mimalloc 3.5.2+ compiles its allocation-sampling profiler in by
# default, which puts a sample countdown on the allocation path. The product
# never uses it, so the production allocator leaves it out (the contract in
# tests/test_prod_allocator_contract.sh checks the banner has no profiling line).
MIMALLOC_CFLAGS = -std=c11 -O2 -w \
-Ivendored/mimalloc/include \
-Ivendored/mimalloc/src \
-DMI_OVERRIDE=1 -DMI_DEBUG=0 \
$(MIMALLOC_OVERRIDE_DEFINE)
-DMI_OVERRIDE=1 -DMI_DEBUG=0 -DMI_PROFILE=0 \
$(MIMALLOC_OVERRIDE_DEFINE) $(MIMALLOC_WIN_DEFINE)
MIMALLOC_CFLAGS_TEST = -std=c11 -g -O1 -w \
-Ivendored/mimalloc/include \
-Ivendored/mimalloc/src \
-DMI_OVERRIDE=0
-DMI_OVERRIDE=0 $(MIMALLOC_WIN_DEFINE)
# sqlite3 (vendored amalgamation — compiled ourselves for ASan instrumentation)
# SQLITE_ENABLE_FTS5: enables the FTS5 full-text search extension used by the
+2
View File
@@ -1037,6 +1037,8 @@ SQLite databases stored at `~/.cache/codebase-memory-mcp/`. Persists across rest
## Troubleshooting
Windows users: see [Windows troubleshooting](docs/WINDOWS_TROUBLESHOOTING.md) for versioned known issues and workarounds.
| Problem | Fix |
|---------|-----|
| `/mcp` doesn't show the server | Check `.mcp.json` path is absolute. Restart agent. Test: `echo '{}' \| /path/to/binary` should output JSON. |
+6 -4
View File
@@ -121,10 +121,12 @@ License summary:
| wyhash | `internal/cbm/vendored/wyhash/` | Unlicense (public domain) | [wangyi-fudan/wyhash](https://github.com/wangyi-fudan/wyhash) |
Local modifications to these libraries are documented next to the
vendored sources (currently only SQLite: `vendored/sqlite3/PATCHES.md`,
raising the Unix VFS `MAX_PATHNAME` ceiling from 512 to 4096 to match
CBM's 4 KiB path support). Patches must be reapplied on every upstream
refresh and are covered by `scripts/vendored-checksums.txt`.
vendored sources: `vendored/sqlite3/PATCHES.md` (raising the Unix VFS
`MAX_PATHNAME` ceiling from 512 to 4096 to match CBM's 4 KiB path support)
and `vendored/mimalloc/PATCHES.md` (the upstream source pin, mimalloc v3.5.3
at commit `d4881d33`, plus the macOS decommit-recommit change and one comment
rewording). Patches must be reapplied on every upstream refresh and are
covered by `scripts/vendored-checksums.txt`.
The graph-UI HTTP server is a first-party implementation
(`src/ui/httpd.c` + `src/ui/http_server.c`) — no third-party HTTP library
+57
View File
@@ -0,0 +1,57 @@
# Windows Troubleshooting
This page collects known Windows-specific symptoms, with the version range each affects, confirmed workarounds, and links to the canonical tracking issues.
## Non-ASCII paths in CBM_CACHE_DIR or CBM_ALLOWED_ROOT
**Symptom:** Configuration appears not to persist, the config database cannot be opened (`error: cannot open config database`), or repositories inside `CBM_ALLOWED_ROOT` are rejected during indexing.
**Affected versions:** Affects v0.9.x and earlier. Fixed in v0.10.0 (environment variables are read as wide strings via `GetEnvironmentVariableW`).
**Workaround:** On an older build, use ASCII-only paths for these two variables or upgrade with `install.ps1`.
**Tracking:** #1165.
## Upgrade completes but binary version does not change
**Symptom:** `codebase-memory-mcp update` reports success and downloads files, but `codebase-memory-mcp --version` still reports the previous version (with `.exe.old` remaining alongside).
**Affected versions:** Affects v0.9.x and earlier (in-process self-update on Windows). From v0.10.0, `update` prints the `install.ps1` command instead of replacing itself.
**Workaround:** On an older build, run `install.ps1` once; it replaces the binary out-of-process and verifies the installed version.
**Tracking:** #1168 (closed; fixed in v0.10.0).
## Index worker hangs, crashes, or leaves an empty worker log
**Symptom:** `index_repository` reports worker hangs or crashes. (An *empty* worker log is a separate, fixed bug: v0.10.4 and earlier.)
**Affected versions:** Empty worker logs affect v0.10.4 and earlier (fixed in v0.10.5). Misleading "crashed on a file" reports for returned errors fixed in v0.10.0. From v0.11.0, the Windows worker is memory-capped. From v0.12.0, a worker that exits without returning a result is no longer counted as a success, and its log is kept. The underlying hang (#1130) and startup exit (#1145) remain open for investigation.
**Workaround:** For repositories containing large binary, media, or model directories, exclude those paths with `.cbmignore` before indexing (see [`.cbmignore` documentation](cbmignore.md)). From v0.10.5, the worker log names the in-flight file; if indexing still fails on v0.11.0 or newer, attach that log to #1130 or #1145.
**Tracking:** #1130, #1132, #1145.
## Antigravity 2.0 configuration not picked up
**Symptom:** Installation completes but Antigravity 2.0 does not see the MCP server or hooks, or the environment is detected as Gemini-CLI; duplicate large binaries may also be left on disk.
**Affected versions:** All releases through v0.12.1 when neither detection signal below is present (v0.9.x checks only for `~/.gemini/antigravity-cli`; from v0.10.0, `antigravity` on PATH also counts). The duplicate binary left behind by `install.ps1` affects v0.9.x and earlier (fixed in v0.10.0).
**Workaround:** Antigravity is configured only when `~/.gemini/antigravity-cli` exists or `antigravity` is on PATH; the target is `~/.gemini/config/mcp_config.json`, and no hooks are installed for it. If neither signal is present, the machine is treated as Gemini-CLI. From v0.10.0, `install.ps1` keeps a single binary; running `codebase-memory-mcp install` manually without `--dir` can still create a second copy in `~/.local/bin`.
**Tracking:** #1161.
## OpenCode installed but not detected
**Symptom:** `codebase-memory-mcp install` detects other clients but skips OpenCode on Windows.
**Affected versions:** v0.9.x and earlier detect OpenCode only through `opencode` on PATH. v0.10.0 also detects its configuration (the config file, `%USERPROFILE%\.config\opencode\`, or `OPENCODE_CONFIG_DIR`), and v0.12.0 also detects its data folder `%USERPROFILE%\.local\share\opencode\`, which the OpenCode CLI and the Desktop app both create, so detection no longer depends on PATH. In v0.12.1 and earlier, a PATH longer than 4 KB hides every CLI from detection, and npm, mise and scoop shim folders are not searched; both are fixed after v0.12.1 (#2381).
**Workaround:** On v0.12.0 or newer, start OpenCode once (CLI or Desktop app) so that its data folder exists, then re-run `codebase-memory-mcp install`. On v0.10.x or v0.11.0, make sure `%USERPROFILE%\.config\opencode\` exists (or point `OPENCODE_CONFIG` at an existing config file, or `OPENCODE_CONFIG_DIR` at an existing folder), then re-run `codebase-memory-mcp install`. On v0.9.x, `opencode` must be on PATH; upgrading with `install.ps1` is the simpler fix.
**Tracking:** #1167.
---
When a release resolves one of these issues, update or remove the corresponding entry rather than letting this page become a second issue tracker.
+7 -2
View File
@@ -30,6 +30,7 @@
#include "foundation/hash_table.h" // CBMHashTable — crash-supervisor quarantine set
#include "foundation/str_util.h" // cbm_json_escape — variants file_path
#include "tree_sitter/api.h" // TSParser, TSNode, TSTree, TSInput, TSLanguage, TSPoint, TSParseOptions, TSParseState
#include "foundation/platform.h"
#include "foundation/constants.h"
#include "mimalloc.h" // mi_malloc/mi_calloc/mi_realloc/mi_free/mi_usable_size — bind 3rd-party allocators (#424)
#if defined(CBM_BIND_TS_ALLOCATOR) && CBM_BIND_TS_ALLOCATOR
@@ -1259,7 +1260,9 @@ static int count_params_from_signature(const char *sig) {
* unlucky in-flight file is never quarantined alone. The env var is set
* solely by the supervisor during recovery — a no-op on normal runs. */
static void cbm_index_mark(const char *rel_path, char event) {
const char *mf = getenv("CBM_INDEX_MARKER_FILE");
char marker_path[CBM_SZ_4K];
const char *mf =
cbm_safe_getenv("CBM_INDEX_MARKER_FILE", marker_path, sizeof(marker_path), NULL);
if (!mf || !mf[0] || !rel_path || !rel_path[0]) {
return;
}
@@ -1296,7 +1299,9 @@ enum { CBM_QSET_UNINIT = 0, CBM_QSET_INITING = 1, CBM_QSET_INITED = 2 };
static atomic_int g_quarantine_state = CBM_QSET_UNINIT;
static void cbm_quarantine_load(void) {
const char *qf = getenv("CBM_INDEX_QUARANTINE_FILE");
char quarantine_path[CBM_SZ_4K];
const char *qf = cbm_safe_getenv("CBM_INDEX_QUARANTINE_FILE", quarantine_path,
sizeof(quarantine_path), NULL);
if (!qf || !qf[0]) {
return; /* normal path: empty set */
}
+6
View File
@@ -313,6 +313,12 @@ typedef struct {
* change of all, store_impact.h follow_variant_keys). NULL on every other
* definition. Tail field: zero-init stays valid. */
const char *variant_key;
/* Go only (#2033): the raw aliased type text of an identity alias
* `type A = B` ("B"). Carried to the cross-file registries so a method
* call on A from another file follows the alias to B's method set. NULL
* for a defined type `type A B` (its method set is its own) and in every
* other language. Tail field: zero-init stays valid. */
const char *alias_target;
} CBMDefinition;
/* Argument captured from a call expression */
+5 -1
View File
@@ -2249,11 +2249,15 @@ static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, C
// method is `new`. The constructor body lives in `initialize`, so a callee
// of "new" never resolves. Redirect to the receiver type name so the call
// links to the class/constructor like every other language's `new T()`.
// Scope-resolved receivers (`Admin::User.new`) get the same treatment —
// the callee carries the full constant path ("Admin::User"), whose bare
// leaf ("User") is what resolution joins on.
if (lang == CBM_LANG_RUBY) {
TSNode m = ts_node_child_by_field_name(node, TS_FIELD("method"));
TSNode recv = ts_node_child_by_field_name(node, TS_FIELD("receiver"));
if (!ts_node_is_null(m) && !ts_node_is_null(recv) &&
strcmp(ts_node_type(recv), "constant") == 0) {
(strcmp(ts_node_type(recv), "constant") == 0 ||
strcmp(ts_node_type(recv), "scope_resolution") == 0)) {
char *mt = cbm_node_text(a, m, source);
if (mt && strcmp(mt, "new") == 0) {
char *rt = cbm_node_text(a, recv, source);
+9
View File
@@ -8299,6 +8299,15 @@ static void emit_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *s
def.decl_namespace = cs_type_decl_namespace(a, node, ctx->source);
}
def.base_classes = extract_base_classes(a, node, ctx->source, ctx->language);
// Go identity alias `type A = B` (#2033): keep the aliased type text so the
// cross-file registries can set alias_of. A defined type `type A B` is a
// type_spec and never gets here with this kind.
if (ctx->language == CBM_LANG_GO && strcmp(kind, "type_alias") == 0) {
TSNode target = ts_node_child_by_field_name(node, TS_FIELD("type"));
if (!ts_node_is_null(target)) {
def.alias_target = cbm_node_text(a, target, ctx->source);
}
}
def.decorators = extract_decorators(a, node, ctx->source, ctx->language, spec);
def.docstring = extract_docstring(ctx, node, name);
+23
View File
@@ -2705,6 +2705,27 @@ static const char **split_pipe_strings(CBMArena *a, const char *text) {
return idx > 0 ? arr : NULL;
}
/* QN of the type an identity alias `type A = B` names, for the def-driven
* cross-file registries (#2033). The per-file AST scan sets alias_of only for
* the file it walks, so an alias declared in another file of the package
* reaches a call only through here. Only a bare identifier is qualified (B
* declared in A's package); a qualified, pointer, composite or generic target
* stays NULL rather than guessed. */
static const char *go_alias_target_qn(CBMArena *arena, const char *target,
const char *def_module_qn) {
if (!target || !target[0] || !def_module_qn || !def_module_qn[0]) {
return NULL;
}
for (const unsigned char *p = (const unsigned char *)target; *p; p++) {
bool ident = (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
(*p >= '0' && *p <= '9') || *p == '_' || *p >= 0x80;
if (!ident) {
return NULL;
}
}
return cbm_arena_sprintf(arena, "%s.%s", def_module_qn, target);
}
// Helper: parse "|"-separated "name:type" field definitions and populate a registered type.
// Format: "Binder:Binder|Name:string|Count:int"
// type_text is resolved relative to def_module_qn.
@@ -3273,6 +3294,7 @@ void cbm_run_go_lsp_cross(CBMArena *arena, const char *source, int source_len,
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
rt.method_names = split_pipe_strings(arena, d->method_names_str);
}
rt.alias_of = go_alias_target_qn(arena, d->alias_target, def_mod);
cbm_registry_add_type(&reg, rt);
@@ -3569,6 +3591,7 @@ CBMTypeRegistry *cbm_go_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, i
if (rt.is_interface && d->method_names_str && d->method_names_str[0]) {
rt.method_names = split_pipe_strings(arena, d->method_names_str);
}
rt.alias_of = go_alias_target_qn(arena, d->alias_target, def_mod);
cbm_registry_add_type(reg, rt);
GO_NOTE_NEW_TYPES();
if (d->field_defs && d->field_defs[0]) {
+4
View File
@@ -102,6 +102,10 @@ typedef struct {
/* Callable identity (#2061): signature suffix of this def's graph QN;
* qualified_name stays the base QN. NULL = none (always, until enabled). */
const char *sig;
/* Go only (#2033): raw aliased type text of `type A = B` ("B"), qualified
* by the registrar against def_module_qn like return_types. NULL = not an
* identity alias. */
const char *alias_target;
} CBMLSPDef;
// Parse source, build registry from defs + stdlib, run LSP.
+134 -17
View File
@@ -269,8 +269,12 @@ static TSNode find_first_kind(TSNode node, const char *kind) {
return null_node;
}
static void ts_emit_resolved_call_at(TSLSPContext *ctx, const char *callee_qn, const char *strategy,
float confidence, TSNode site) {
/* `reason` carries the name written at the site when it differs from the
* callee's leaf (`bar()` through `import { foo as bar }`): the pipeline joins an
* alias strategy by that name instead of by the callee QN's last segment. */
static void ts_emit_resolved_call_reason_at(TSLSPContext *ctx, const char *callee_qn,
const char *strategy, float confidence,
const char *reason, TSNode site) {
if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn)
return;
CBMResolvedCall rc = {0};
@@ -278,7 +282,7 @@ static void ts_emit_resolved_call_at(TSLSPContext *ctx, const char *callee_qn, c
rc.callee_qn = callee_qn;
rc.strategy = strategy ? strategy : "lsp_ts";
rc.confidence = confidence;
rc.reason = NULL;
rc.reason = reason;
rc.kind = CBM_RESOLVED_INVOCATION;
if (!ts_node_is_null(site)) {
rc.site_start_byte = ts_node_start_byte(site);
@@ -287,8 +291,14 @@ static void ts_emit_resolved_call_at(TSLSPContext *ctx, const char *callee_qn, c
cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc);
}
static void ts_emit_resolved_reference(TSLSPContext *ctx, const char *callee_qn,
const char *strategy, float confidence, TSNode site) {
static void ts_emit_resolved_call_at(TSLSPContext *ctx, const char *callee_qn, const char *strategy,
float confidence, TSNode site) {
ts_emit_resolved_call_reason_at(ctx, callee_qn, strategy, confidence, NULL, site);
}
static void ts_emit_resolved_reference_reason(TSLSPContext *ctx, const char *callee_qn,
const char *strategy, float confidence,
const char *reason, TSNode site) {
if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn ||
ts_node_is_null(site)) {
return;
@@ -298,13 +308,18 @@ static void ts_emit_resolved_reference(TSLSPContext *ctx, const char *callee_qn,
rc.callee_qn = callee_qn;
rc.strategy = strategy ? strategy : "lsp_ts_value_reference";
rc.confidence = confidence;
rc.reason = NULL;
rc.reason = reason;
rc.kind = CBM_RESOLVED_CALL_REFERENCE;
rc.site_start_byte = ts_node_start_byte(site);
rc.site_end_byte = ts_node_end_byte(site);
cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc);
}
static void ts_emit_resolved_reference(TSLSPContext *ctx, const char *callee_qn,
const char *strategy, float confidence, TSNode site) {
ts_emit_resolved_reference_reason(ctx, callee_qn, strategy, confidence, NULL, site);
}
static void ts_emit_unresolved_call_at(TSLSPContext *ctx, const char *expr_text, const char *reason,
TSNode site) {
if (!ctx || !ctx->resolved_calls || !ctx->enclosing_func_qn)
@@ -1425,6 +1440,19 @@ static bool ts_scope_has_binding(const CBMScope *scope, const char *name) {
return ts_scope_binding_owner(scope, name) != NULL;
}
/* The member an import binding names in its module: the exported name of a
* renamed named import (`foo` for `import { foo as bar }`, #2297), else the
* binding itself. Callers have already matched `local` to an import binding. */
static const char *ts_import_export_name(const TSLSPContext *ctx, const char *local) {
if (!ctx || !local)
return local;
for (int i = 0; i < ctx->import_alias_count; i++) {
if (strcmp(ctx->import_alias_locals[i], local) == 0)
return ctx->import_alias_exports[i];
}
return local;
}
/* Full QN of an imported symbol. An import value is normally the module QN
* (symbol = module + "." + name), but may already be the symbol QN. When the
* module QN ends in ".name" the spelling is ambiguous: `Foo.ts` exporting
@@ -1463,8 +1491,9 @@ static const CBMRegisteredFunc *ts_exact_same_module_function(TSLSPContext *ctx,
}
/* Return a function only when the local import binding maps to one unambiguous
* module and that module contains the exact local-name QN. Aliases whose export
* name is unavailable in CBMImport deliberately fail closed and remain USAGE. */
* module and that module contains the exact QN of the member the binding names
* (the exported name for `import { foo as bar }`). A renamed import of a name
* the module does not export fails closed and remains USAGE. */
static const CBMRegisteredFunc *ts_exact_imported_function(TSLSPContext *ctx, const char *name) {
if (!ctx || !ctx->registry || !name || ts_scope_has_binding(ctx->current_scope, name)) {
return NULL;
@@ -1484,7 +1513,8 @@ static const CBMRegisteredFunc *ts_exact_imported_function(TSLSPContext *ctx, co
if (!matched_module) {
return NULL;
}
const char *full_qn = ts_import_symbol_qn(ctx, matched_module, name);
const char *full_qn =
ts_import_symbol_qn(ctx, matched_module, ts_import_export_name(ctx, name));
return full_qn ? cbm_registry_lookup_func(ctx->registry, full_qn) : NULL;
}
@@ -1532,7 +1562,7 @@ static const CBMType *type_of_identifier(TSLSPContext *ctx, const char *name) {
// Construct the full symbol QN using the same convention as exact
// imported function-value resolution.
const char *full_qn = ts_import_symbol_qn(ctx, mqn, name);
const char *full_qn = ts_import_symbol_qn(ctx, mqn, ts_import_export_name(ctx, name));
// Try the symbol lookup precedence: registered func, then registered type.
const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, full_qn);
@@ -2087,7 +2117,8 @@ static const CBMType *ts_new_bare_class_type(TSLSPContext *ctx, const char *cnam
bool import_matched = false;
const char *import_module = ts_import_module_for_local(ctx, cname, &import_matched);
if (import_module)
return cbm_type_named(ctx->arena, ts_import_symbol_qn(ctx, import_module, cname));
return cbm_type_named(
ctx->arena, ts_import_symbol_qn(ctx, import_module, ts_import_export_name(ctx, cname)));
return cbm_type_named(ctx->arena, local_qn);
}
@@ -2208,7 +2239,8 @@ static const CBMType *ts_signature_for_call(TSLSPContext *ctx, TSNode function,
if (!registered) {
const char *module_qn = ts_import_module_for_local(ctx, name, NULL);
if (module_qn)
registered = ts_lookup_symbol_for_call(ctx, module_qn, name, args);
registered = ts_lookup_symbol_for_call(ctx, module_qn,
ts_import_export_name(ctx, name), args);
}
if (registered)
return registered->signature;
@@ -2566,7 +2598,8 @@ static const CBMType *resolve_type_with_imports(TSLSPContext *ctx, const CBMType
continue;
if (strcmp(lname, bare) != 0)
continue;
return cbm_type_named(ctx->arena, ts_import_symbol_qn(ctx, mqn, bare));
return cbm_type_named(ctx->arena,
ts_import_symbol_qn(ctx, mqn, ts_import_export_name(ctx, bare)));
}
return t;
}
@@ -2933,12 +2966,19 @@ static void resolve_value_references_at(TSLSPContext *ctx, TSNode call_node) {
}
const CBMRegisteredFunc *target = ts_exact_same_module_function(ctx, name);
const char *strategy = "lsp_ts_same_module_value_reference";
const char *reason = NULL;
if (!target) {
target = ts_exact_imported_function(ctx, name);
strategy = "lsp_ts_import_value_reference";
/* `import { foo as bar }`: the site names `bar`, the target is `foo`. */
if (target && ts_import_export_name(ctx, name) != name) {
strategy = "lsp_callable_value_reference";
reason = name;
}
}
if (target) {
ts_emit_resolved_reference(ctx, target->qualified_name, strategy, 0.95f, arg);
ts_emit_resolved_reference_reason(ctx, target->qualified_name, strategy, 0.95f, reason,
arg);
}
}
}
@@ -3025,14 +3065,20 @@ static void resolve_call_at(TSLSPContext *ctx, TSNode call_node) {
const char *lname = ctx->import_local_names ? ctx->import_local_names[i] : NULL;
const char *mqn = ctx->import_module_qns ? ctx->import_module_qns[i] : NULL;
if (lname && mqn && strcmp(lname, name) == 0) {
const CBMRegisteredFunc *f = ts_lookup_symbol_for_call(ctx, mqn, name, args);
const char *member = ts_import_export_name(ctx, name);
const CBMRegisteredFunc *f = ts_lookup_symbol_for_call(ctx, mqn, member, args);
if (f && member != name) {
ts_emit_resolved_call_reason_at(ctx, f->qualified_name, "lsp_import_alias",
0.95f, name, call_node);
return;
}
if (f) {
ts_emit_resolved_call_at(ctx, f->qualified_name, "lsp_ts_import", 0.95f,
call_node);
return;
}
// Fall through to qualified_name fallback.
const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mqn, name);
const char *qn = cbm_arena_sprintf(ctx->arena, "%s.%s", mqn, member);
ts_emit_unresolved_call_at(ctx, qn, "import_symbol_not_in_registry", call_node);
return;
}
@@ -3374,7 +3420,13 @@ static void resolve_jsx_element(TSLSPContext *ctx, TSNode element_node) {
* per-file row deliberately keeps the file eligible for the
* cross-file pass, where resolved import QNs and project defs are
* available. */
const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, mqn, tag_name);
const char *member = ts_import_export_name(ctx, tag_name);
const CBMRegisteredFunc *f = cbm_registry_lookup_symbol(ctx->registry, mqn, member);
if (f && member != tag_name) {
ts_emit_resolved_call_reason_at(ctx, f->qualified_name, "lsp_import_alias", 0.95f,
tag_name, tag);
return;
}
if (f) {
ts_emit_resolved_call_at(ctx, f->qualified_name, "lsp_ts_jsx_import", 0.95f, tag);
return;
@@ -4005,6 +4057,68 @@ void ts_lsp_add_import(TSLSPContext *ctx, const char *local_name, const char *mo
ctx->import_count = new_count;
}
static void ts_lsp_add_import_alias(TSLSPContext *ctx, const char *local, const char *exported) {
if ((size_t)ctx->import_alias_count == ctx->import_alias_capacity) {
/* The arena retains old arrays: grow geometrically so collecting A
* aliases retains O(A) pointers instead of O(A squared). */
size_t capacity = ctx->import_alias_capacity ? ctx->import_alias_capacity * 2 : 8;
const char **locals =
(const char **)cbm_arena_alloc(ctx->arena, capacity * sizeof(const char *));
const char **exports =
(const char **)cbm_arena_alloc(ctx->arena, capacity * sizeof(const char *));
if (!locals || !exports)
return;
for (int i = 0; i < ctx->import_alias_count; i++) {
locals[i] = ctx->import_alias_locals[i];
exports[i] = ctx->import_alias_exports[i];
}
ctx->import_alias_locals = locals;
ctx->import_alias_exports = exports;
ctx->import_alias_capacity = capacity;
}
ctx->import_alias_locals[ctx->import_alias_count] = local;
ctx->import_alias_exports[ctx->import_alias_count] = exported;
ctx->import_alias_count++;
}
/* Record each `import { foo as bar }` binding of the file (#2297). The import
* map holds only local bindings, so a renamed binding spelled `<module>.bar`,
* named nothing, and every call through it lost its CALLS edge. Reading the
* file's own import statements serves the per-file and both cross-file entry
* points alike. `default as x` keeps today's local-name lookup (a default
* import binds no exported member name); string export names and identity
* aliases need no entry. Must run before the type passes, which resolve
* imported type names. */
static void ts_lsp_collect_import_aliases(TSLSPContext *ctx, TSNode root) {
if (!ctx || !ctx->arena || ts_node_is_null(root) || ctx->import_count == 0)
return;
uint32_t kn = 0;
TSNode *kids = collect_children(ctx->arena, root, &kn);
for (uint32_t i = 0; i < kn; i++) {
if (!node_kind_is(kids[i], "import_statement"))
continue;
TSNode named = find_first_kind(find_first_kind(kids[i], "import_clause"), "named_imports");
if (ts_node_is_null(named))
continue;
uint32_t sn = 0;
TSNode *specs = collect_children(ctx->arena, named, &sn);
for (uint32_t j = 0; j < sn; j++) {
if (!node_kind_is(specs[j], "import_specifier"))
continue;
TSNode name = ts_node_child_by_field_name(specs[j], "name", TS_LSP_FIELD_LEN("name"));
TSNode alias =
ts_node_child_by_field_name(specs[j], "alias", TS_LSP_FIELD_LEN("alias"));
if (!node_kind_is(name, "identifier") || !node_kind_is(alias, "identifier"))
continue;
char *exported = node_text(ctx, name);
char *local = node_text(ctx, alias);
if (exported && local && strcmp(exported, "default") != 0 &&
strcmp(exported, local) != 0)
ts_lsp_add_import_alias(ctx, local, exported);
}
}
}
// ── Stdlib seeds (Phase 5 will replace with generator) ────────────────────────
static const CBMType *tt_builtin(CBMArena *a, const char *n) {
@@ -5948,6 +6062,7 @@ void cbm_run_ts_lsp(CBMArena *arena, CBMFileResult *result, const char *source,
ts_lsp_add_import(&ctx, imp->local_name, imp->module_path);
}
}
ts_lsp_collect_import_aliases(&ctx, root);
ast_sweep_shapes(&ctx, root, &reg);
refine_ast_signatures(&ctx, root, &reg);
@@ -6201,6 +6316,7 @@ void cbm_run_ts_lsp_cross_with_registry(CBMArena *arena, const char *source, int
ts_lsp_add_import(&ctx, import_names[i], import_qns[i]);
}
}
ts_lsp_collect_import_aliases(&ctx, root);
ast_sweep_shapes(&ctx, root, &overlay);
refine_ast_signatures(&ctx, root, &overlay);
@@ -6416,6 +6532,7 @@ void cbm_run_ts_lsp_cross(CBMArena *arena, const char *source, int source_len,
ts_lsp_add_import(&ctx, import_names[i], import_qns[i]);
}
}
ts_lsp_collect_import_aliases(&ctx, root);
ast_sweep_shapes(&ctx, root, &reg);
refine_ast_signatures(&ctx, root, &reg);
+8
View File
@@ -33,6 +33,14 @@ typedef struct {
const char **import_module_qns;
int import_count;
// Renamed named imports (`import { foo as bar }`): local binding -> the name
// the module exports. Parallel arrays of length import_alias_count; a
// binding absent here is exported under its local name.
const char **import_alias_locals;
const char **import_alias_exports;
int import_alias_count;
size_t import_alias_capacity;
// File / surrounding context.
const char *module_qn; // QN of this file's module
const char *enclosing_func_qn; // current function being walked, NULL at module scope
+1
View File
@@ -279,6 +279,7 @@ static void cr_walk_def(cr_ctx_t *c, CBMDefinition *d) {
cr_list(c, &d->extra_props);
cr_str(c, &d->decl_namespace);
cr_str(c, &d->variant_key);
cr_str(c, &d->alias_target);
}
static void cr_walk_call(cr_ctx_t *c, CBMCall *call) {
+1 -1
View File
@@ -40,7 +40,7 @@ sbom = {
"packages": [
{"SPDXID": "SPDXRef-Package-sqlite3", "name": "sqlite3", "versionInfo": "3.51.3", "licenseDeclared": "blessing", "downloadLocation": "https://sqlite.org", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-yyjson", "name": "yyjson", "versionInfo": "0.12.0", "licenseDeclared": "MIT", "downloadLocation": "https://github.com/ibireme/yyjson", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-mimalloc", "name": "mimalloc", "versionInfo": "3.3.2", "licenseDeclared": "MIT", "downloadLocation": "https://github.com/microsoft/mimalloc", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-mimalloc", "name": "mimalloc", "versionInfo": "3.5.3", "licenseDeclared": "MIT", "downloadLocation": "https://github.com/microsoft/mimalloc", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-xxhash", "name": "xxhash", "versionInfo": "0.8.3", "licenseDeclared": "BSD-2-Clause", "downloadLocation": "https://github.com/Cyan4973/xxHash", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-tre", "name": "tre", "versionInfo": "0.8.0", "licenseDeclared": "BSD-2-Clause", "downloadLocation": "https://github.com/laurikari/tre", "filesAnalyzed": False},
{"SPDXID": "SPDXRef-Package-tree-sitter", "name": "tree-sitter", "versionInfo": "0.24.4", "licenseDeclared": "MIT", "downloadLocation": "https://github.com/tree-sitter/tree-sitter", "filesAnalyzed": False},
+8
View File
@@ -496,6 +496,14 @@ make -j"$NPROC" -f Makefile.cbm cbm TEST_SEAMS=1 ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}
WATCHDOG_BINARY="$ROOT/$BUILD_DIR/codebase-memory-mcp"
CBM_TEST_BINARY="$WATCHDOG_BINARY" bash "$ROOT/tests/test_parent_watchdog.sh"
# Step 5-alloc: the production allocator object Step 5 just built. The unit
# suites link mimalloc's debug build without the global override, so they cannot
# see free(NULL) before the allocator's constructor (mimalloc #1341, PR #2562)
# or the vendored local patches; this links small fixtures against
# prod_mimalloc.o itself.
echo "=== Step 5-alloc: production allocator contract (mimalloc #1341 + local patches) ==="
bash "$ROOT/tests/test_prod_allocator_contract.sh" "$ROOT/$BUILD_DIR"
# Step 5a: that watchdog is also the SMALLEST-stack thread in the image, which
# makes it the first casualty when static TLS grows — glibc takes the TLS block
# out of each thread's own stack allocation. Checked here, against the binary
+44 -38
View File
@@ -992,50 +992,56 @@ a86b7735823b0408a4f3b922be86d4d25b6f4ab0dab32e03fb77246ca439dcf2 internal/cbm/v
9b4bc8245565c98ccfc61c07749928b57e7c0f6fddb0530c4f6aa1971893d88b internal/cbm/vendored/zstd/zstd.h
66a8c3f71d12ea6e797e4f622f31f3f8f81c41b36f48cad4f5de7d8bfb6aac0a internal/cbm/vendored/zstd/zstd_errors.h
82ade5d7d9b029044b5fbbc326207fc47c2d44ee4dd71e8a559c36d728217de9 vendored/mimalloc/LICENSE
dd4f25cae53209d45d73f8e6a2b9c219e8fc7434d97f20eba9af1a8b850030fd vendored/mimalloc/include/mimalloc-new-delete.h
edb95ac4f912bbcde6c1b658e147e757896c881eae249dbb5bbbae66ebdf6deb vendored/mimalloc/PATCHES.md
0a9c7e6daea2814876eab49697922f98b630c27ab9b089fbe4bd74f6e56edd68 vendored/mimalloc/include/mimalloc-new-delete.h
21fcf61c4443341ac6bf6ea528af31dc7267e8e3456fc64bfd07704503032175 vendored/mimalloc/include/mimalloc-override.h
5260df227ea1612b8678a06b063cc8f48e739b551554541cdeb4d430a7e257b3 vendored/mimalloc/include/mimalloc-stats.h
869161433ab2d807a75752d0762accf5b68b6b24ce67e417af46bf181b9b6c72 vendored/mimalloc/include/mimalloc.h
e9512b521a82cc0cbc262f6060dd70bb4243ac4a7d69d6ab8b1ef8e56f181a21 vendored/mimalloc/include/mimalloc/atomic.h
57a7297e18ae464a7bfd3fc25a46229008602385697305c69ed289b89ece7631 vendored/mimalloc/include/mimalloc/bits.h
5aeeff21d667daf4aadcc66f634aa957ecd688d15b277828f367c8c2274251a8 vendored/mimalloc/include/mimalloc/internal.h
555302d2fcd05e974441d0c9c9208c26e860db4b3f17b9025a08f42e0f4807ba vendored/mimalloc/include/mimalloc/prim.h
1f378b4b6063c078d2aeb5d916c1d30b5db787a44ab9118e7daf673b086882c6 vendored/mimalloc/include/mimalloc/track.h
b54e760da2a8d9d8a9cb708e280268c2d194c99bc518b21f72db70100578ad6a vendored/mimalloc/include/mimalloc/types.h
70420b4430da4bb6b5197fd27c6c0df485249c53a986bc9536c9fb643fc7613b vendored/mimalloc/src/alloc-aligned.c
6c8e9b0d042481c55791edb7330daec64bedaf26f0e70c9ca40de3cc17a690fe vendored/mimalloc/src/alloc-override.c
98d0eb877d7b9cdf7e9f857aaf3c739b69f746b91221e0e427e82bdd7cec90b3 vendored/mimalloc/src/alloc-posix.c
a92d58e47220e5b6d138b83d92498771be4195ee59136a134e4461f6f726b9b8 vendored/mimalloc/src/alloc.c
86fde644ede2e8d582e53499568a1713516a20326e0350eaa06a28029cece91b vendored/mimalloc/src/arena-meta.c
a1d6900d2b9a17461aa2a72a7791dfb22f6167b86f7ac6f15f981d9735aa2adb vendored/mimalloc/src/arena.c
de16033e029488e9ade4152ea1274e1a5ecbdc2c8a8d8ee50b745fd037ee6f6c vendored/mimalloc/src/bitmap.c
2e7a54ff44592a1fe8fe929e691322622f8aa1e0b1e80954781f332a88dd6cd1 vendored/mimalloc/src/bitmap.h
c9a845dd7b9020f26d73761d235fb75e7bc4a3682576de750050ac4098211b43 vendored/mimalloc/src/free.c
ae8e88737c9861e8e071fe5f1e7acc52093bcffd136a359a6b473101e968c14f vendored/mimalloc/src/heap.c
eca1fe77d72ab2aaccc447c8119056e9c4e8851bda9a95472b573539f76107ad vendored/mimalloc/src/init.c
ebd95b475f15d5387e6ef3c88a9cb47b88e64d7e55998ae1dc656614676a443b vendored/mimalloc/src/libc.c
192ce06ac9076445d223205fb340d8a1fc818dbf2b4c4a801dfd2c10bc6c1162 vendored/mimalloc/src/options.c
011e965f28c0e2c885a6834d51bd756a458dd37ebfed6fd4d9ad6607ce0390ed vendored/mimalloc/src/os.c
1be3a208c678fc34187c2a8ffb75f3fe1eecc674bbf8b2af4d1f329506b1fad5 vendored/mimalloc/src/page-map.c
7ec275805c9f00eb66710ffa99817b3ed1dba9826435d08f4866f7935152f6ae vendored/mimalloc/src/page-queue.c
d3614a59f2407a32e75b051deb1aff31b233f587d325dcc5420ba508551b687e vendored/mimalloc/src/page.c
7488e9e562ca85d2d757ec48e4d334935263a2333aa05b6061d8520f427c5890 vendored/mimalloc/src/prim/emscripten/prim.c
ce14eb6dfd55f241095b55f70983931a2d92f7a6a622a9b360cdd70ab3b53208 vendored/mimalloc/src/prim/osx/alloc-override-zone.c
c0aabbf2fbe54ce55e7dee23cb1891c2d65e0e1f5b18d9fe1a4e733c0c91d996 vendored/mimalloc/include/mimalloc-profile.h
57aaf0b0db23521a32b0c6d7e0969e37ce69466fcfb0cb9b2d0114c2b56be182 vendored/mimalloc/include/mimalloc-stats.h
8ec1bff9902bab6682e3ecbf7613ccc915e082ca743fdd0c396d3954323fe8bf vendored/mimalloc/include/mimalloc.h
7be611200328a99b052eda81abb8ad78b7e03446a0f6d302c1eb31cc9f096398 vendored/mimalloc/include/mimalloc/atomic.h
1ecfb1bd1a7b6f73c335d21f3411c9ce0de3cbf6710e2d209e6cb6df2baf1ed3 vendored/mimalloc/include/mimalloc/bits.h
26c10f7b990c51629618c74c64e3263a1fe00da787c2e72c56a79f87393f602c vendored/mimalloc/include/mimalloc/internal.h
a701ed0c182b4d0039bf683b4ac197011b8b794971a616c76ca64f036bc30d8b vendored/mimalloc/include/mimalloc/prim-tls.h
1987e8e2eedc07bb181bf2a11a27bec80a5309c32cfa66a56900fb4cbb64b172 vendored/mimalloc/include/mimalloc/prim.h
31d94e981640bc4c340f21887610a9cd7242af4c5adc578f722ad03cb852db2e vendored/mimalloc/include/mimalloc/track.h
01abbaf1cc5572675b97f4115f205335e319b93249ee5163fb3bf2ad0fbbceae vendored/mimalloc/include/mimalloc/types.h
8390ff8d0e6c2954db1d95bfc1a4388b302605a1c5102c0495e5163838186e7f vendored/mimalloc/src/alloc-aligned.c
6576949f30978f3b7544d9696af12dac9615074a5c2fc05e3681475575bc6ca3 vendored/mimalloc/src/alloc-override.c
a6dcdef4694964c972e6cf37f197f93c95f321566a3b5ccc1761129257255e93 vendored/mimalloc/src/alloc-posix.c
e074a8a622f6317d42eb43e48db8cde3345696c96d6749523ef4fd58aeb55bb7 vendored/mimalloc/src/alloc.c
27fce7003a5108fb9d0c61e92e0cd7870f687f7e3cf12ad19c7fb3954b2a0bc2 vendored/mimalloc/src/arena.c
b33f7b220358667fb0eddcd6569ec7b79f7ef61daf08448c8448c8d1a8741b09 vendored/mimalloc/src/bitmap.c
f3a9375ec0b56d515bea4b23732bf9a04964efecc516eb8aaac8b58ebf4512d9 vendored/mimalloc/src/bitmap.h
56a1c57bd08c49359e19e8cdcd2a11b50c30bb12a53063375374fb3f5c8d6189 vendored/mimalloc/src/free.c
a33d339568e5a7d16d6bae5650a79b1aef649e9b4ad0261be982580c2971eb64 vendored/mimalloc/src/heap.c
1452ca684eca6d959eec9eae91750162f7738055f995bd4863f79b87aab88a8c vendored/mimalloc/src/init.c
b774d5750409006b690ab6262f037d78362ca978729314493636b358249e7fbd vendored/mimalloc/src/libc.c
5bfe80a0c473371d299840e8ed20831601d4998e4164e2e0bf71303697395824 vendored/mimalloc/src/options.c
06b2d6d8a4cf3bfcc1822bb09aeaa4e7b741e7a5241f70b4f693b8038f455b00 vendored/mimalloc/src/os.c
ff3509ae3d4185e9cb2a95e1f35daba3329e998bac917ac7938f1bf06dba0f78 vendored/mimalloc/src/page-map.c
4128ca8b8d4df1116f73bf9a397903ca9941701c635345b1241295c3a31ff47d vendored/mimalloc/src/page-queue.c
ad0df1217e96669ef63539c345ea7dc4d637c3269be2d4de36ac3a5a42fb0c12 vendored/mimalloc/src/page.c
896fc1016a26e10c6cf96259d0524c57e123ac9dcef1ba04f23d0add445286dd vendored/mimalloc/src/prim/emscripten/prim.c
502efbc8616f18be83713759438a0e5b8017ed215947ddfd48e74b85fd899114 vendored/mimalloc/src/prim/osx/alloc-override-zone.c
247a9952465eb105be03a9962e922b085ce5d52034775551a61cd8594275be73 vendored/mimalloc/src/prim/osx/prim.c
cc771788e5ba591efb681829a8724f8662a1f50f3db39cf5ebe7738b8ed57e44 vendored/mimalloc/src/prim/prim.c
792531421c247fca0766ff7630057ec491173578ca4d1919ae34f05eb289ccf4 vendored/mimalloc/src/prim/readme.md
20594d38ef4fc0c641f20a8664d17ee03ce195ca21b44e036f43cdb7bcd0bd0d vendored/mimalloc/src/prim/unix/prim.c
bee9c655a17e1e93bf4908e0a902fd3b566628447b1446fcbfa92bca2e76ca15 vendored/mimalloc/src/prim/wasi/prim.c
884443f66808143d645dc6254c38e27f91b540a667ea23fe0ba1ef1caaf2b114 vendored/mimalloc/src/prim/prim-tls.c
44419a450e742812d8f3c881d957bdd6cf18f40b90dfe1153084d2b64666f12d vendored/mimalloc/src/prim/prim.c
28510fce6710d7d08224135e2e986e85995d7907232d3d6e278a82213ca78935 vendored/mimalloc/src/prim/readme.md
a5ce291e50e965fbeedc632f54bc9f10b78c0548cf7739cd477e21c6e2751b3a vendored/mimalloc/src/prim/unix/prim.c
2bc862cddb522314d58b3c6ef041e101c25cf04dfdda35cf78f91dbb6140cf46 vendored/mimalloc/src/prim/wasi/prim.c
f70e61a1f2fe63da2f51c581895cf4a0918a9bffb0cf8600171f297141446962 vendored/mimalloc/src/prim/windows/etw-mimalloc.wprp
4f3110ef2054c95cb275be96cb279224d3d46a728c5117bd1435425f382de778 vendored/mimalloc/src/prim/windows/etw.h
445e6a70f9728fcc76a5ba52079336064d5e2257c2969023f1c64bf943776f54 vendored/mimalloc/src/prim/windows/etw.man
5cde60fcef94cd79ab816030be00f8c1bdf18fab343563508c681ec6e762d1f1 vendored/mimalloc/src/prim/windows/prim.c
41d96c081cbda6bcad0e8270eab7c3d69f94dfe3d3dcd8a9567569c595984cb0 vendored/mimalloc/src/prim/windows/prim.c
1bcb43eef7fcddf6d8f18c6d5ee9bd350090ed183a1009d4f1c9b4d81a205bb9 vendored/mimalloc/src/prim/windows/readme.md
a96a1acc75fd6595811b28420cf0495b87fac98c4dbc73aa0d742b6824f607c8 vendored/mimalloc/src/random.c
4059be2a6676693be3433f5aedc711f56301772e65f3573654c236d6d1cfd074 vendored/mimalloc/src/static.c
16825bbc252de422982b5728df7eda181d952824f5e3ce958c7fc85feae64b8e vendored/mimalloc/src/stats.c
7f76bcace4b0578f2063477752138adabd99f7b10a276998e296a312c47a7cfe vendored/mimalloc/src/theap.c
49be69a4b674e09690a63870830f3ffb05698ed4210949ef5add11957a08325b vendored/mimalloc/src/threadlocal.c
2bd12844f9de0392fdb00ea70f5ad15649ffd8e49f9a4d2388e15d6469a7b703 vendored/mimalloc/src/random.c
4edc7700c2dd911cd03919d1098e973d887900ab08a5f41fd9648a79517fca57 vendored/mimalloc/src/sample-guarded.c
3df74fee931244a302293f7d215dc87e3b5c120ab7df207edd92d0ff19eb3729 vendored/mimalloc/src/sample-profile.c
fadfb75216098793f9f81e57d6ff3b63914084976526ed7867c9ca212825d784 vendored/mimalloc/src/static.c
d78e0467008946b40d5356315a9ba76e04354ead65b1aafabbb7bd091c3826a6 vendored/mimalloc/src/stats.c
41c147e8eb6e960687b79a201dd4498fb80f58923a0a4d73e04e80ef315b8eb6 vendored/mimalloc/src/subproc.c
91271bc76502c743d109f32201c02271f63a9f6ec51ef207f8b533f6de39df27 vendored/mimalloc/src/theap.c
01dbaacd5ac6685dc59c40d82a3f8932e5290d4d05f9a441424e4ea53e1ba973 vendored/mimalloc/src/threadlocal.c
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 vendored/nomic/LICENSE
893a74d9e3f0a74358344edc999e165739df343b36297967f3fd9cd462022f39 vendored/nomic/NOTICE
dab3009c0d76b0c5e05ad8abc7c9f8f6effca547fea3c0394be96418a85c1081 vendored/nomic/code_tokens.h
+3 -1
View File
@@ -810,7 +810,9 @@ static bool cli_activation_production_context_init(cli_activation_production_con
context->cleanup_ok = true;
context->deadline_ms = cli_activation_deadline_after(CLI_ACTIVATION_DRAIN_TIMEOUT_MS);
context->control_deadline_ms = cli_activation_deadline_after(CLI_ACTIVATION_CONTROL_TIMEOUT_MS);
const char *original_cache_environment = getenv("CBM_CACHE_DIR");
char original_cache_buffer[CLI_BUF_4K];
const char *original_cache_environment = cbm_safe_getenv("CBM_CACHE_DIR", original_cache_buffer,
sizeof(original_cache_buffer), NULL);
context->original_cache_environment_present = original_cache_environment != NULL;
if (context->original_cache_environment_present) {
context->original_cache_environment = strdup(original_cache_environment);
+3 -5
View File
@@ -166,11 +166,9 @@ static inline int cbm_setenv(const char *name, const char *value, int overwrite)
free(wide_value);
return EINVAL;
}
/* Keep the CRT's narrow environment useful for legacy getenv callers,
* then repair the process-wide Windows environment with the actual UTF-16
* value. _putenv_s alone routes UTF-8 path bytes through the active ANSI
* code page, which corrupts non-ASCII cache roots inherited by children. */
int status = _putenv_s(name, value);
/* _putenv_s can reject UTF-8 bytes outside the active ANSI code page.
* Narrow getenv returns bytes in that code page after this wide update. */
int status = _wputenv_s(wide_name, wide_value);
if (status == 0 && !SetEnvironmentVariableW(wide_name, wide_value)) {
status = EINVAL;
}
+251 -16
View File
@@ -1902,6 +1902,8 @@ struct cbm_mcp_server {
void *auto_index_count_test_context;
cbm_mcp_snapshot_read_test_hook_fn snapshot_read_test_hook;
void *snapshot_read_test_context;
cbm_mcp_snippet_capture_test_hook_fn snippet_capture_test_hook;
void *snippet_capture_test_context;
#endif
bool read_batch_tool; /* this request's store reads run in a read batch */
size_t search_output_limit_override;
@@ -2272,6 +2274,16 @@ void cbm_mcp_server_set_snapshot_read_test_hook(cbm_mcp_server_t *srv,
srv->snapshot_read_test_hook = hook;
srv->snapshot_read_test_context = context;
}
void cbm_mcp_server_set_snippet_capture_test_hook(cbm_mcp_server_t *srv,
cbm_mcp_snippet_capture_test_hook_fn hook,
void *context) {
if (!srv) {
return;
}
srv->snippet_capture_test_hook = hook;
srv->snippet_capture_test_context = context;
}
#endif
/* ── Cache dir + project DB path helpers ───────────────────────── */
@@ -7063,10 +7075,161 @@ static coverage_path_result_t coverage_normalize_rel(const char *input, bool all
return written > 0U || allow_root ? COVERAGE_PATH_OK : COVERAGE_PATH_INVALID;
}
static const char *coverage_path_freshness(cbm_store_t *store, const char *project,
const char *root_path, const char *rel_path,
bool *outside) {
/* #2585: the store generation ("u<db_uid>g<mutation_gen>") advances on every
* index run, incremental ones included, and on every publication. It is read
* when the response is built and names the publication current then; it does
* not make the response one atomic read of that publication.
* metadata.generation names the coverage-recording run and stays fixed across
* incremental re-indexes. Empty when unreadable. */
static void mcp_add_store_generation(yyjson_mut_doc *doc, yyjson_mut_val *obj, cbm_store_t *store) {
char generation[QUERY_GRAPH_GENERATION_CAP] = "";
if (!store || cbm_store_generation(store, generation, sizeof(generation)) != CBM_STORE_OK) {
generation[0] = '\0';
}
yyjson_mut_obj_add_strcpy(doc, obj, "store_generation", generation);
}
/* #2585: file_hashes.sha256 is the SHA-256 of the raw bytes the indexer read
* (pipeline_incremental.c semantic_manifest_hash_file). Recomputing it is the
* only way to say the bytes on disk are the bytes that were indexed: mtime and
* size miss a same-size edit whose timestamp was preserved (cp -p, rsync -t,
* archive extraction), and go stale when identical bytes are rewritten. */
static bool mcp_file_sha256_hex(const char *abs_path, char out[CBM_SHA256_HEX_LEN + 1]) {
FILE *file = cbm_fopen(abs_path, "rb");
if (!file) {
return false;
}
cbm_sha256_ctx sha;
cbm_sha256_init(&sha);
unsigned char buf[CBM_SZ_4K];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
cbm_sha256_update(&sha, buf, len);
}
bool valid = !ferror(file);
if (fclose(file) != 0) {
valid = false;
}
if (!valid) {
return false;
}
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&sha, digest);
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < CBM_SHA256_DIGEST_LEN; i++) {
out[i * 2] = hex[digest[i] >> 4];
out[i * 2 + 1] = hex[digest[i] & 0x0f];
}
out[CBM_SHA256_HEX_LEN] = '\0';
return true;
}
/* True when the recorded content hash is a well-formed SHA-256 the current
* bytes can be compared against. */
static bool mcp_recorded_sha256(const char *sha) {
if (!sha || strlen(sha) != CBM_SHA256_HEX_LEN) {
return false;
}
for (int i = 0; i < CBM_SHA256_HEX_LEN; i++) {
if (!((sha[i] >= '0' && sha[i] <= '9') || (sha[i] >= 'a' && sha[i] <= 'f'))) {
return false;
}
}
return true;
}
/* #2585: read a whole file into *bytes (released with cbm_sb_free), so that
* the bytes a response serves are the bytes whose hash was checked. */
static bool mcp_read_file_bytes(const char *abs_path, cbm_sb_t *bytes) {
cbm_sb_init(bytes);
FILE *file = cbm_fopen(abs_path, "rb");
if (!file) {
return false;
}
char buf[CBM_SZ_4K];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
cbm_sb_append_n(bytes, buf, len);
}
bool valid = !ferror(file) && !bytes->oom;
if (fclose(file) != 0) {
valid = false;
}
if (!valid) {
cbm_sb_free(bytes);
}
return valid;
}
/* Lines [start, end] of an in-memory file, with read_file_lines' semantics
* (text-mode line endings, NULL when the range is empty). */
static char *mcp_slice_lines(cbm_sb_t *bytes, int start, int end) {
size_t begin = bytes->len;
size_t stop = bytes->len;
int lineno = 1;
for (size_t i = 0; i < bytes->len; i++) {
if (lineno >= start && begin == bytes->len) {
begin = i;
}
if (bytes->buf[i] == '\n') {
if (lineno >= end) {
stop = i + 1;
break;
}
lineno++;
}
}
if (begin >= stop) {
return NULL;
}
char saved = bytes->buf[stop];
bytes->buf[stop] = '\0';
char *slice = heap_strdup(bytes->buf + begin);
bytes->buf[stop] = saved;
#ifdef _WIN32
/* read_file_lines opens in text mode. */
if (slice) {
char *out = slice;
for (const char *in = slice; *in; in++) {
if (!(in[0] == '\r' && in[1] == '\n')) {
*out++ = *in;
}
}
*out = '\0';
}
#endif
return slice;
}
/* A path is current when its bytes were verified to be the indexed bytes,
* whatever its recorded metadata says. A metadata match whose content could not
* be verified is not: it is the verdict the hash check exists to replace. */
static bool coverage_freshness_current(const char *freshness, bool content_verified) {
return content_verified && freshness &&
(strcmp(freshness, "metadata_match") == 0 || strcmp(freshness, "content_match") == 0);
}
/* Freshness of one tracked path. With a recorded content hash the verdict is
* by content: "metadata_match" (metadata and bytes agree), "content_changed"
* (metadata agrees, bytes differ), "content_match" (metadata is stale, bytes
* are identical) or "metadata_changed" (both differ). Without one it falls
* back to metadata alone and *content_verified stays false.
*
* When captured is non-NULL, the file is read once into it (released with
* cbm_sb_free) and the verdict is about exactly those bytes, so a caller
* serving them cannot race a concurrent write. *captured_ok stays false when
* the path never reached the file (missing, outside, not tracked,
* unreadable). */
static const char *coverage_path_freshness_capture(cbm_store_t *store, const char *project,
const char *root_path, const char *rel_path,
bool *outside, bool *content_verified,
cbm_sb_t *captured, bool *captured_ok) {
*outside = false;
*content_verified = false;
if (captured) {
cbm_sb_init(captured);
*captured_ok = false;
}
if (!root_path || !root_path[0]) {
return "unavailable";
}
@@ -7099,8 +7262,35 @@ static const char *coverage_path_freshness(cbm_store_t *store, const char *proje
return "unavailable";
}
bool matches = hash.mtime_ns == info.mtime_ns && hash.size == info.size;
char current[CBM_SHA256_HEX_LEN + 1] = "";
bool hashed = false;
if (captured) {
*captured_ok = mcp_read_file_bytes(abs_path, captured);
if (*captured_ok) {
cbm_sha256_hex(captured->buf ? captured->buf : "", captured->len, current);
hashed = true;
}
} else {
hashed = mcp_recorded_sha256(hash.sha256) && mcp_file_sha256_hex(abs_path, current);
}
bool verifiable = mcp_recorded_sha256(hash.sha256) && hashed;
bool same_bytes = verifiable && strcmp(current, hash.sha256) == 0;
cbm_store_clear_file_hash(&hash);
return matches ? "metadata_match" : "metadata_changed";
if (!verifiable) {
return matches ? "metadata_match" : "metadata_changed";
}
*content_verified = true;
if (matches) {
return same_bytes ? "metadata_match" : "content_changed";
}
return same_bytes ? "content_match" : "metadata_changed";
}
static const char *coverage_path_freshness(cbm_store_t *store, const char *project,
const char *root_path, const char *rel_path,
bool *outside, bool *content_verified) {
return coverage_path_freshness_capture(store, project, root_path, rel_path, outside,
content_verified, NULL, NULL);
}
/* Read an "start-end,start-end,...[,+<N>]" string into a JSON ranges array.
@@ -7270,8 +7460,9 @@ static const char *coverage_status(const cbm_coverage_row_t *rows, int count,
return "no_recorded_issue";
}
static const char *coverage_recommended_action(const char *status, const char *freshness) {
if (!freshness || strcmp(freshness, "metadata_match") != 0) {
static const char *coverage_recommended_action(const char *status, const char *freshness,
bool content_verified) {
if (!coverage_freshness_current(freshness, content_verified)) {
return "read_source_and_reindex";
}
if (strcmp(status, "partial") == 0) {
@@ -7368,6 +7559,7 @@ static char *handle_check_index_coverage(cbm_mcp_server_t *srv, const char *args
yyjson_mut_obj_add_int(doc, meta_obj, "coverage_version",
have_meta ? meta.coverage_version : 0);
yyjson_mut_obj_add_bool(doc, meta_obj, "generation_matches", generation_matches);
mcp_add_store_generation(doc, meta_obj, store);
yyjson_mut_obj_add_val(doc, root, "metadata", meta_obj);
yyjson_mut_val *path_results = yyjson_mut_arr(doc);
@@ -7407,18 +7599,22 @@ static char *handle_check_index_coverage(cbm_mcp_server_t *srv, const char *args
yyjson_mut_obj_add_str(doc, item, "coverage_lookup", "error");
}
bool outside = false;
const char *freshness = coverage_path_freshness(
store, project, have_project ? proj.root_path : NULL, rel, &outside);
bool exact_path_verified =
have_meta && meta.hash_records_complete && strcmp(freshness, "metadata_match") == 0;
bool content_verified = false;
const char *freshness =
coverage_path_freshness(store, project, have_project ? proj.root_path : NULL, rel,
&outside, &content_verified);
bool exact_path_verified = have_meta && meta.hash_records_complete &&
coverage_freshness_current(freshness, content_verified);
const char *status =
outside ? "outside_project"
: coverage_status(rows, row_count, rel, recording_status,
generation_matches, lookup_ok, exact_path_verified);
yyjson_mut_obj_add_strcpy(doc, item, "status", status);
yyjson_mut_obj_add_strcpy(doc, item, "freshness", freshness);
yyjson_mut_obj_add_strcpy(doc, item, "recommended_action",
coverage_recommended_action(status, freshness));
yyjson_mut_obj_add_bool(doc, item, "content_verified", content_verified);
yyjson_mut_obj_add_strcpy(
doc, item, "recommended_action",
coverage_recommended_action(status, freshness, content_verified));
yyjson_mut_val *coverage = yyjson_mut_arr(doc);
for (int i = 0; i < row_count; i++) {
coverage_add_row_json(doc, coverage, &rows[i], rel, diagnostics_full);
@@ -13327,8 +13523,10 @@ static char *snippet_pdf_text(const cbm_node_t *node) {
return out;
}
/* #2585: when captured is non-NULL the lines come from those already-read
* bytes instead of a fresh read of the file. */
static char *resolve_snippet_source(const char *root_path, const char *file_path, int start,
int end, char **out_abs_path) {
int end, cbm_sb_t *captured, char **out_abs_path) {
*out_abs_path = NULL;
if (!root_path || !file_path) {
return NULL;
@@ -13339,7 +13537,8 @@ static char *resolve_snippet_source(const char *root_path, const char *file_path
*out_abs_path = abs_path;
if (cbm_path_within_root(root_path, abs_path)) {
return read_file_lines(abs_path, start, end);
return captured ? mcp_slice_lines(captured, start, end)
: read_file_lines(abs_path, start, end);
}
return NULL;
}
@@ -13824,15 +14023,44 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
if (!outline && end < original_end) {
snippet_clipped = true;
}
/* #2585: start/end are indexed line numbers. Slice the file only when its
* bytes are the indexed bytes; otherwise the same range can hold another
* symbol's code. The file is read once: the slice comes from the same
* bytes whose hash was checked, so a concurrent write cannot slip in
* between. Unverifiable files keep the previous behavior and say so
* through content_verified. */
const char *source_freshness = NULL;
bool source_content_verified = false;
bool source_drift = false;
cbm_sb_t captured;
cbm_sb_init(&captured);
bool captured_ok = false;
if (!outline && !snippet_is_pdf(node->file_path) && root_path && node->file_path) {
bool outside = false;
source_freshness = coverage_path_freshness_capture(
srv->store, node->project, root_path, node->file_path, &outside,
&source_content_verified, &captured, &captured_ok);
source_drift = source_content_verified &&
!coverage_freshness_current(source_freshness, source_content_verified);
#ifdef CBM_ENABLE_TEST_SEAMS
if (srv->snippet_capture_test_hook) {
srv->snippet_capture_test_hook(srv->snippet_capture_test_context, node->file_path);
}
#endif
}
char *abs_path = NULL;
char *source = NULL;
if (outline) {
if (outline || source_drift) {
source = NULL;
} else if (snippet_is_pdf(node->file_path)) {
source = snippet_pdf_text(node); /* never the file's bytes */
} else {
source = resolve_snippet_source(root_path, node->file_path, start, end, &abs_path);
/* Without captured bytes (not tracked, unreadable) the file is read
* here, and the snippet is never content-verified. */
source = resolve_snippet_source(root_path, node->file_path, start, end,
captured_ok ? &captured : NULL, &abs_path);
}
cbm_sb_free(&captured);
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val *root_obj = yyjson_mut_obj(doc);
@@ -13852,6 +14080,13 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
yyjson_mut_obj_add_str(doc, root_obj, "file_path", display_path);
yyjson_mut_obj_add_int(doc, root_obj, "start_line", start);
yyjson_mut_obj_add_int(doc, root_obj, "end_line", end);
mcp_add_store_generation(doc, root_obj, srv->store);
yyjson_mut_obj_add_bool(doc, root_obj, "content_verified", source_content_verified);
if (source_drift) {
yyjson_mut_obj_add_bool(doc, root_obj, "source_drift", true);
yyjson_mut_obj_add_strcpy(doc, root_obj, "freshness", source_freshness);
yyjson_mut_obj_add_str(doc, root_obj, "recommended_action", "reindex_then_retry");
}
if (snippet_clipped) {
yyjson_mut_obj_add_bool(doc, root_obj, "source_truncated", true);
yyjson_mut_obj_add_bool(doc, root_obj, "source_clipped", true); /* compatibility */
+7
View File
@@ -115,6 +115,13 @@ void cbm_mcp_server_set_snapshot_read_test_hook(cbm_mcp_server_t *srv,
cbm_mcp_snapshot_read_test_hook_fn hook,
void *context);
/* Runs after get_code_snippet has read and verified a file's bytes and before
* it slices them, so a test can write the file at exactly that point (#2585). */
typedef void (*cbm_mcp_snippet_capture_test_hook_fn)(void *context, const char *rel_path);
void cbm_mcp_server_set_snippet_capture_test_hook(cbm_mcp_server_t *srv,
cbm_mcp_snippet_capture_test_hook_fn hook,
void *context);
/* Filesystem PATH_MAX prevents a portable end-to-end fixture for multi-KiB
* stored paths. This seam drives the normal search result ownership,
* directory aggregation, and renderer pipeline with synthetic identities. */
+6 -2
View File
@@ -34,8 +34,10 @@
#include "yyjson/yyjson.h"
/* 3: Go defs carry their parsed result list `|`-joined in "ret" (F-G8 D1);
* a version-2 surface holds the raw result text and must be re-extracted. */
enum { SURFACE_CODEC_VERSION = 3 };
* a version-2 surface holds the raw result text and must be re-extracted.
* 4: Go identity aliases carry their target in "al" (#2033); a version-3
* surface has no alias targets and must be re-extracted. */
enum { SURFACE_CODEC_VERSION = 4 };
/* Labels the incremental name registry serves that pxc_map_label does NOT
* carry into the CBMLSPDef set. Their (name, qn, label) triple must still
@@ -394,6 +396,7 @@ static char *surface_file_to_json(const CBMFileResult *result, CBMLanguage langu
yyjson_mut_obj_add_bool(doc, o, "ir", d->is_rust_impl_relation);
yyjson_mut_obj_add_bool(doc, o, "ab", d->is_abstract);
add_str_array_or_null(doc, o, "dec", d->decorators, -1);
add_str_or_null(doc, o, "al", d->alias_target);
yyjson_mut_arr_add_val(lsp, o);
}
yyjson_mut_obj_add_val(doc, root, "lsp", lsp);
@@ -670,6 +673,7 @@ int cbm_lsp_surface_defs_from_json(CBMArena *arena, const char *defs_json, CBMLS
d->is_abstract = yyjson_get_bool(yyjson_obj_get(o, "ab"));
int dec_count = 0;
d->decorators = arena_str_array(arena, yyjson_obj_get(o, "dec"), true, &dec_count);
d->alias_target = arena_str_or_null(arena, yyjson_obj_get(o, "al"));
if (!d->qualified_name || !d->short_name || !d->label) {
/* A def the writer could not have produced: qn/sn/label are
* unconditionally present in build_lsp_def output. Corrupt row. */
+3
View File
@@ -715,6 +715,9 @@ static int pxc_build_lsp_def(CBMArena *arena, const CBMDefinition *src, const ch
}
}
dst->lang = lang;
dst->alias_target = (lang == CBM_LANG_GO && src->alias_target)
? cbm_arena_strdup(arena, src->alias_target)
: NULL;
dst->decorators = NULL;
if (src->decorators) {
int n = 0;
+1 -1
View File
@@ -2559,7 +2559,7 @@ static void log_result_census(const char *tag, CBMFileResult **cache, int file_c
str_def_misc += census_len(def->route_path) + census_len(def->route_method) +
census_len(def->impl_trait) + census_len(def->http_base_url) +
census_list(def->extra_props) + census_len(def->decl_namespace) +
census_len(def->variant_key);
census_len(def->variant_key) + census_len(def->alias_target);
}
for (int c = 0; c < r->calls.count; c++) {
const CBMCall *call = &r->calls.items[c];
+88
View File
@@ -7,6 +7,7 @@
*/
#include "test_framework.h"
#include "cbm.h"
#include "foundation/platform.h"
#include "foundation/constants.h" /* CBM_SZ_* */
#include "preprocessor.h" /* cbm_export_macro_candidates (#1989) */
#include "../src/foundation/compat.h" /* cbm_clock_gettime (wide-flat scaling guard) */
@@ -856,6 +857,57 @@ TEST(ruby_module) {
PASS();
}
/* A Ruby `Const.new` call is rewritten to name the CONSTANT, not the method:
* the constructor body lives in `initialize`, so the call should link the class
* like every other language's `new T()`. The rewrite fired only for bare
* `constant` receivers, so a scope-resolved receiver kept the whole call text
* as its callee ("Admin::User.new"), which resolves to nothing and cannot join
* the Ruby LSP's constructor rows, whose callee is the class QN.
*
* Both spellings must land on the bare constant path; a retained ".new" suffix
* is the bug's signature. */
TEST(ruby_constructor_callee_is_constant_path) {
CBMFileResult *r = extract("module Admin\n class User\n end\nend\n"
"class Widget\nend\n"
"class Maker\n"
" def build\n"
" Admin::User.new\n"
" Widget.new\n"
" end\n"
"end\n",
CBM_LANG_RUBY, "t", "maker.rb");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
int scoped = 0, bare = 0, kept_new = 0;
for (int i = 0; i < r->calls.count; i++) {
const char *callee = r->calls.items[i].callee_name;
if (strcmp(callee, "Admin::User") == 0)
scoped = 1;
if (strcmp(callee, "Widget") == 0)
bare = 1;
/* Either failure mode: the method name survived on its own, or the
* whole `Recv.new` expression did. */
size_t n = strlen(callee);
if (strcmp(callee, "new") == 0 || (n >= 4 && strcmp(callee + n - 4, ".new") == 0))
kept_new = 1;
}
if (!scoped || !bare || kept_new) {
printf(" ruby callees (%d):\n", r->calls.count);
for (int i = 0; i < r->calls.count; i++)
printf(" '%s'\n", r->calls.items[i].callee_name);
}
/* Scope-resolved receiver reduces to the constant path; its bare leaf is
* what resolution joins on. */
ASSERT(scoped);
/* Bare receiver: pre-existing behaviour, guarded against regression. */
ASSERT(bare);
ASSERT_FALSE(kept_new);
cbm_free_result(r);
PASS();
}
/* --- C# --- */
TEST(csharp_class) {
CBMFileResult *r = extract("namespace App { public class Service { public void Run() {} public "
@@ -13373,7 +13425,42 @@ TEST(python_namespace_facts_multiple_bindings_and_unsupported_effects) {
PASS();
}
#ifdef _WIN32
/* The supervisor sets this path through cbm_setenv. Exercise the actual
* journal writer, which must read it as UTF-8 before calling cbm_fopen. */
TEST(extraction_marker_round_trips_non_ascii_environment_path) {
char dir[512] = "/tmp/cbm-marker-XXXXXX";
ASSERT_NOT_NULL(cbm_mkdtemp(dir));
char marker[640];
snprintf(marker, sizeof(marker), "%s/marker-\xce\x94-\xe4\xb8\x81.log", dir);
char saved[4096];
const char *previous = cbm_safe_getenv("CBM_INDEX_MARKER_FILE", saved, sizeof(saved), NULL);
ASSERT_EQ(cbm_setenv("CBM_INDEX_MARKER_FILE", marker, 1), 0);
cbm_index_mark_start("synthetic.cpp");
cbm_index_mark_done("synthetic.cpp");
if (previous) {
(void)cbm_setenv("CBM_INDEX_MARKER_FILE", saved, 1);
} else {
(void)cbm_unsetenv("CBM_INDEX_MARKER_FILE");
}
FILE *file = cbm_fopen(marker, "rb");
char contents[128] = {0};
if (file) {
(void)fread(contents, 1, sizeof(contents) - 1, file);
(void)fclose(file);
}
(void)cbm_unlink(marker);
(void)cbm_rmdir(dir);
ASSERT_NOT_NULL(file);
ASSERT_STR_EQ(contents, "S synthetic.cpp\nD synthetic.cpp\n");
PASS();
}
#endif
SUITE(extraction) {
#ifdef _WIN32
RUN_TEST(extraction_marker_round_trips_non_ascii_environment_path);
#endif
RUN_TEST(extract_compact_keeps_every_field_and_shrinks_the_arena);
RUN_TEST(extract_compact_is_idempotent_and_survives_empty_results);
RUN_TEST(extract_spill_round_trip_keeps_every_field);
@@ -13491,6 +13578,7 @@ SUITE(extraction) {
RUN_TEST(php_function);
RUN_TEST(ruby_class);
RUN_TEST(ruby_module);
RUN_TEST(ruby_constructor_callee_is_constant_path);
RUN_TEST(csharp_class);
RUN_TEST(csharp_interface);
RUN_TEST(csharp_extension_block_members);
+123
View File
@@ -1378,6 +1378,127 @@ TEST(golsp_crossfile_aliased_field_requal) {
PASS();
}
/* #2033: an identity alias declared in another file of the same package.
* `type FeatureSet = bitmap` lives in one file, `bitmap.Set` in a second, the
* call `reqs.Set` in a third, so none of it is in the calling file's AST and
* only the defs can carry the alias. A defined type (`type Defined bitmap`)
* must not inherit bitmap's methods. The decoy `other.Set` keeps a name-only
* match from passing. Both cross-file registries are checked: the per-file
* one and the prebuilt one shared by the parallel resolve. */
static CBMLSPDef go_crossfile_alias_defs[] = {
{.qualified_name = "test.main.bitmap",
.short_name = "bitmap",
.label = "Class",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.bitmap.Set",
.short_name = "Set",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.bitmap"},
{.qualified_name = "test.main.FeatureSet",
.short_name = "FeatureSet",
.label = "Type",
.def_module_qn = "test.main",
.alias_target = "bitmap"},
{.qualified_name = "test.main.Defined",
.short_name = "Defined",
.label = "Class",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.Framework",
.short_name = "Framework",
.label = "Struct",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.Framework.NewFeatureSet",
.short_name = "NewFeatureSet",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.Framework",
.return_types = "FeatureSet"},
{.qualified_name = "test.main.Framework.NewDefined",
.short_name = "NewDefined",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.Framework",
.return_types = "Defined"},
{.qualified_name = "test.main.other",
.short_name = "other",
.label = "Struct",
.def_module_qn = "test.main"},
{.qualified_name = "test.main.other.Set",
.short_name = "Set",
.label = "Method",
.def_module_qn = "test.main",
.receiver_type = "test.main.other"},
};
static const char *go_crossfile_alias_source = "package main\n\n"
"func useAlias(f *Framework) {\n"
"\treqs := f.NewFeatureSet()\n"
"\treqs.Set(3)\n}\n\n"
"func useDefined(f *Framework) {\n"
"\td := f.NewDefined()\n"
"\td.Set(3)\n}\n";
static int go_crossfile_alias_check(const CBMResolvedCallArray *out) {
int idx = find_resolved_arr_confident(out, "useAlias", ".Set");
if (idx < 0 || strcmp(out->items[idx].callee_qn, "test.main.bitmap.Set") != 0) {
printf(" resolved calls (%d):\n", out->count);
for (int i = 0; i < out->count; i++) {
const CBMResolvedCall *rc = &out->items[i];
printf(" %s -> %s [%s %.2f]\n", rc->caller_qn ? rc->caller_qn : "(null)",
rc->callee_qn ? rc->callee_qn : "(null)", rc->strategy ? rc->strategy : "(null)",
rc->confidence);
}
}
ASSERT_GTE(idx, 0);
ASSERT_STR_EQ(out->items[idx].callee_qn, "test.main.bitmap.Set");
/* Reached through alias_of, so the emitter reports it the way it reports
* a same-file alias today (lsp_embed_dispatch): typed, not a name guess. */
ASSERT_TRUE(strncmp(out->items[idx].strategy, "lsp_", 4) == 0);
ASSERT_TRUE(strcmp(out->items[idx].strategy, "lsp_unresolved") != 0);
ASSERT_TRUE(out->items[idx].confidence >= 0.9f);
/* A defined type has its own (empty) method set. */
ASSERT_EQ(find_resolved_arr_confident(out, "useDefined", "bitmap.Set"), -1);
return 0;
}
TEST(golsp_crossfile_type_alias_method) {
int n = (int)(sizeof(go_crossfile_alias_defs) / sizeof(go_crossfile_alias_defs[0]));
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
cbm_run_go_lsp_cross(&arena, go_crossfile_alias_source, (int)strlen(go_crossfile_alias_source),
"test.main", go_crossfile_alias_defs, n, NULL, NULL, 0, NULL, &out);
if (go_crossfile_alias_check(&out) != 0) {
cbm_arena_destroy(&arena);
return 1;
}
cbm_arena_destroy(&arena);
PASS();
}
TEST(golsp_crossfile_type_alias_method_prebuilt) {
int n = (int)(sizeof(go_crossfile_alias_defs) / sizeof(go_crossfile_alias_defs[0]));
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
CBMTypeRegistry *reg = cbm_go_build_cross_registry(&arena, go_crossfile_alias_defs, n);
ASSERT_NOT_NULL(reg);
cbm_run_go_lsp_cross_with_registry(&arena, go_crossfile_alias_source,
(int)strlen(go_crossfile_alias_source), "test.main", reg,
NULL, NULL, 0, NULL, &out);
if (go_crossfile_alias_check(&out) != 0) {
cbm_arena_destroy(&arena);
return 1;
}
cbm_arena_destroy(&arena);
PASS();
}
TEST(golsp_crossfile_local_interface_single_impl) {
const char *source =
"package main\n\n"
@@ -1542,6 +1663,8 @@ SUITE(go_lsp) {
RUN_TEST(golsp_crossfile_interface_dispatch);
RUN_TEST(golsp_crossfile_interface_field_chain);
RUN_TEST(golsp_crossfile_aliased_field_requal);
RUN_TEST(golsp_crossfile_type_alias_method);
RUN_TEST(golsp_crossfile_type_alias_method_prebuilt);
RUN_TEST(golsp_crossfile_map_index);
RUN_TEST(golsp_crossfile_stdlib_interface);
RUN_TEST(golsp_crossfile_local_interface_single_impl);
+42
View File
@@ -2122,7 +2122,49 @@ static int tf_role_dispatch(tf_phase_t phase, int argc, char **argv) {
const char *cbm_test_program_path;
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define TF_MSAN_MIMALLOC_OUTPUT 1
#endif
#endif
#ifdef TF_MSAN_MIMALLOC_OUTPUT
#include <mimalloc.h>
#include <sanitizer/msan_interface.h>
/* MSan lane only. mimalloc is deliberately left uninstrumented there
* (build/msan/mimalloc.o is compiled without -fsanitize=memory), so MSan never
* sees it write its own stack. Its text output is the one place those stack
* bytes reach an MSan interceptor: a message formatted in a mimalloc stack
* buffer arrives at fputs still carrying the shadow an earlier instrumented
* frame left there, and MSan reports initialized bytes as uninitialized.
* mimalloc 3.5.3 prints an arena-alignment fallback warning on the x86-64 MSan
* lane, which tripped this in the mem suite. Routing mimalloc's output through
* this handler marks the finished text initialized before it reaches fputs. */
static bool tf_msan_mi_output_live;
static void tf_msan_mi_output(const char *msg, void *arg) {
(void)arg;
/* Registering replays mimalloc's delayed-output buffer into the handler.
* Its constructor already wrote all of that to stderr before main, so the
* replay would only repeat it behind a stray newline: skip it. */
if (!tf_msan_mi_output_live || !msg) {
return;
}
/* Mark the text initialized before reading even its first byte. */
__msan_unpoison_string(msg);
if (!msg[0]) {
return;
}
(void)fputs(msg, stderr);
}
#endif
int main(int argc, char **argv) {
#ifdef TF_MSAN_MIMALLOC_OUTPUT
mi_register_output(tf_msan_mi_output, NULL);
tf_msan_mi_output_live = true;
#endif
cbm_test_program_path = tf_runner_image(argc, argv);
int role_rc = tf_role_dispatch(TF_PHASE_RAW, argc, argv);
if (role_rc >= 0) {
+297 -1
View File
@@ -10,6 +10,7 @@
#include "../src/foundation/log.h"
#include "../src/foundation/platform.h" /* cbm_file_size */
#include "../src/foundation/mem.h" /* cbm_mem_set_budget_for_tests — over-budget seam */
#include "../src/foundation/sha256.h"
#include "../src/foundation/str_util.h"
#include "../src/foundation/subprocess.h"
#include "../src/foundation/workspace.h"
@@ -7183,7 +7184,16 @@ TEST(tool_check_index_coverage_accepts_truncated_ignored_catalog_for_fresh_path_
* metadata_match contract below would fail. */
cbm_path_info_t path_info;
ASSERT_EQ(cbm_path_info_utf8(source_path, &path_info), 0);
ASSERT_EQ(cbm_store_upsert_file_hash(store, "test-project", "main.go", "", path_info.mtime_ns,
/* The real content hash, as the indexer records it: a path counts as
* current only when its bytes are verified (#2585). */
char content[4096];
FILE *source = fopen(source_path, "rb");
ASSERT_NOT_NULL(source);
size_t content_len = fread(content, 1, sizeof(content), source);
fclose(source);
char sha[CBM_SHA256_HEX_LEN + 1];
cbm_sha256_hex(content, content_len, sha);
ASSERT_EQ(cbm_store_upsert_file_hash(store, "test-project", "main.go", sha, path_info.mtime_ns,
path_info.size),
CBM_STORE_OK);
cbm_project_t project = {0};
@@ -7339,6 +7349,286 @@ TEST(tool_check_index_coverage_requires_source_when_file_metadata_changed) {
PASS();
}
/* #2585 fixtures: record main.go's hash row with its real size and either its
* real or a shifted mtime, and either its real content hash or another
* file's. This reaches each freshness state without touching file times, which
* some platforms cannot set precisely. */
static int record_main_go_hash(cbm_store_t *store, const char *tmp, bool real_content,
bool real_mtime) {
char path[512];
snprintf(path, sizeof(path), "%s/project/main.go", tmp);
cbm_path_info_t info;
if (cbm_path_info_utf8(path, &info) != 0) {
return CBM_STORE_ERR;
}
FILE *fp = fopen(path, "rb");
if (!fp) {
return CBM_STORE_ERR;
}
char bytes[4096];
size_t len = fread(bytes, 1, sizeof(bytes), fp);
fclose(fp);
char sha[CBM_SHA256_HEX_LEN + 1];
if (real_content) {
cbm_sha256_hex(bytes, len, sha);
} else {
cbm_sha256_hex("other bytes", strlen("other bytes"), sha);
}
int64_t mtime_ns = real_mtime ? info.mtime_ns : info.mtime_ns - (int64_t)CBM_NSEC_PER_SEC;
return cbm_store_upsert_file_hash(store, "test-project", "main.go", sha, mtime_ns, info.size);
}
static yyjson_val *coverage_first_path(cbm_mcp_server_t *srv, yyjson_doc **doc, char **inner) {
char *response = cbm_mcp_handle_tool(srv, "check_index_coverage",
"{\"project\":\"test-project\",\"paths\":[\"main.go\"],"
"\"format\":\"json\"}");
*inner = response ? extract_text_content(response) : NULL;
free(response);
*doc = *inner ? yyjson_read(*inner, strlen(*inner), 0) : NULL;
return *doc ? yyjson_arr_get(yyjson_obj_get(yyjson_doc_get_root(*doc), "paths"), 0) : NULL;
}
/* #2585: freshness judged by mtime and size passed a same-size edit whose
* timestamp was preserved, and stayed stale after identical bytes were
* rewritten. With a recorded content hash it now reports the bytes. */
TEST(tool_check_index_coverage_freshness_verifies_content_issue2585) {
char tmp[256];
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
ASSERT_NOT_NULL(srv);
cbm_store_t *store = cbm_mcp_server_store(srv);
ASSERT_NOT_NULL(store);
cbm_project_t project = {0};
ASSERT_EQ(cbm_store_get_project(store, "test-project", &project), CBM_STORE_OK);
ASSERT_EQ(write_coverage_meta(store, project.indexed_at, "complete"), CBM_STORE_OK);
cbm_project_free_fields(&project);
static const struct {
bool real_content;
bool real_mtime;
const char *freshness;
const char *action;
} cases[] = {
/* Same metadata, different bytes: previously metadata_match. */
{false, true, "content_changed", "read_source_and_reindex"},
{true, true, "metadata_match", "use_graph_with_best_effort_caveat"},
/* Stale metadata, identical bytes: previously metadata_changed. */
{true, false, "content_match", "use_graph_with_best_effort_caveat"},
{false, false, "metadata_changed", "read_source_and_reindex"},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
ASSERT_EQ(record_main_go_hash(store, tmp, cases[i].real_content, cases[i].real_mtime),
CBM_STORE_OK);
yyjson_doc *doc = NULL;
char *inner = NULL;
yyjson_val *path = coverage_first_path(srv, &doc, &inner);
ASSERT_NOT_NULL(path);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(path, "freshness")), cases[i].freshness);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(path, "recommended_action")), cases[i].action);
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(path, "content_verified")));
yyjson_doc_free(doc);
free(inner);
}
/* A row without a usable content hash keeps the metadata verdict and says
* the content was not verified. */
ASSERT_EQ(cbm_store_upsert_file_hash(store, "test-project", "main.go", "fixture", 0, 0),
CBM_STORE_OK);
yyjson_doc *doc = NULL;
char *inner = NULL;
yyjson_val *path = coverage_first_path(srv, &doc, &inner);
ASSERT_NOT_NULL(path);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(path, "freshness")), "metadata_changed");
ASSERT_FALSE(yyjson_get_bool(yyjson_obj_get(path, "content_verified")));
yyjson_doc_free(doc);
free(inner);
/* Matching metadata alone is not evidence the bytes are current: without
* a usable hash, the path must not be recommended as if it were. */
char main_go[512];
snprintf(main_go, sizeof(main_go), "%s/project/main.go", tmp);
cbm_path_info_t info;
ASSERT_EQ(cbm_path_info_utf8(main_go, &info), 0);
ASSERT_EQ(cbm_store_upsert_file_hash(store, "test-project", "main.go", "fixture", info.mtime_ns,
info.size),
CBM_STORE_OK);
path = coverage_first_path(srv, &doc, &inner);
ASSERT_NOT_NULL(path);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(path, "freshness")), "metadata_match");
ASSERT_FALSE(yyjson_get_bool(yyjson_obj_get(path, "content_verified")));
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(path, "recommended_action")),
"read_source_and_reindex");
yyjson_doc_free(doc);
free(inner);
cbm_mcp_server_free(srv);
cleanup_snippet_dir(tmp);
PASS();
}
static char *snippet_process_order(cbm_mcp_server_t *srv, yyjson_doc **doc) {
char *response = cbm_mcp_handle_tool(
srv, "get_code_snippet",
"{\"project\":\"test-project\","
"\"qualified_name\":\"test-project.cmd.server.main.ProcessOrder\",\"format\":\"json\"}");
char *inner = response ? extract_text_content(response) : NULL;
free(response);
*doc = inner ? yyjson_read(inner, strlen(inner), 0) : NULL;
return inner;
}
/* #2585 (and #1750): the snippet's line range is the indexed range. When the
* file's bytes are not the indexed bytes, the same range can hold another
* symbol's code, so the source is withheld rather than misattributed. */
TEST(tool_get_code_snippet_withholds_source_whose_content_drifted_issue2585) {
char tmp[256];
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
ASSERT_NOT_NULL(srv);
cbm_store_t *store = cbm_mcp_server_store(srv);
ASSERT_NOT_NULL(store);
/* Same metadata, different bytes: mtime/size cannot see this, and the
* indexed range would be sliced from bytes that were never indexed. */
ASSERT_EQ(record_main_go_hash(store, tmp, false, true), CBM_STORE_OK);
yyjson_doc *doc = NULL;
char *inner = snippet_process_order(srv, &doc);
ASSERT_NOT_NULL(doc);
yyjson_val *root = yyjson_doc_get_root(doc);
ASSERT_NULL(strstr(yyjson_get_str(yyjson_obj_get(root, "source")), "func"));
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(root, "source_drift")));
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(root, "freshness")), "content_changed");
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(root, "recommended_action")), "reindex_then_retry");
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(root, "content_verified")));
yyjson_doc_free(doc);
free(inner);
/* The indexed bytes, with current or stale metadata, are served. */
for (int real_mtime = 1; real_mtime >= 0; real_mtime--) {
ASSERT_EQ(record_main_go_hash(store, tmp, true, real_mtime == 1), CBM_STORE_OK);
inner = snippet_process_order(srv, &doc);
ASSERT_NOT_NULL(doc);
root = yyjson_doc_get_root(doc);
ASSERT_NOT_NULL(
strstr(yyjson_get_str(yyjson_obj_get(root, "source")), "func ProcessOrder"));
ASSERT_NULL(yyjson_obj_get(root, "source_drift"));
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(root, "content_verified")));
yyjson_doc_free(doc);
free(inner);
}
/* Without a usable hash the previous behavior holds, marked unverified. */
ASSERT_EQ(cbm_store_upsert_file_hash(store, "test-project", "main.go", "", 0, 0), CBM_STORE_OK);
inner = snippet_process_order(srv, &doc);
ASSERT_NOT_NULL(doc);
root = yyjson_doc_get_root(doc);
ASSERT_NOT_NULL(strstr(yyjson_get_str(yyjson_obj_get(root, "source")), "func ProcessOrder"));
ASSERT_FALSE(yyjson_get_bool(yyjson_obj_get(root, "content_verified")));
yyjson_doc_free(doc);
free(inner);
cbm_mcp_server_free(srv);
cleanup_snippet_dir(tmp);
PASS();
}
#ifdef CBM_ENABLE_TEST_SEAMS
/* Rewrites main.go so that the indexed ProcessOrder range holds other code. */
static void snippet_capture_rewrite(void *context, const char *rel_path) {
(void)rel_path;
FILE *fp = fopen((const char *)context, "w");
if (fp) {
fputs("package main\n\n// a\n// b\n// c\n// d\nfunc Intruder() {\n}\n", fp);
fclose(fp);
}
}
/* #2585: the bytes get_code_snippet serves must be the bytes it verified. A
* write landing after the hash check and before the slice must not produce a
* content_verified snippet holding code that was never indexed. */
TEST(tool_get_code_snippet_serves_the_bytes_it_verified_issue2585) {
char tmp[256];
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
ASSERT_NOT_NULL(srv);
cbm_store_t *store = cbm_mcp_server_store(srv);
ASSERT_NOT_NULL(store);
ASSERT_EQ(record_main_go_hash(store, tmp, true, true), CBM_STORE_OK);
char main_go[512];
snprintf(main_go, sizeof(main_go), "%s/project/main.go", tmp);
cbm_mcp_server_set_snippet_capture_test_hook(srv, snippet_capture_rewrite, main_go);
yyjson_doc *doc = NULL;
char *inner = snippet_process_order(srv, &doc);
cbm_mcp_server_set_snippet_capture_test_hook(srv, NULL, NULL);
ASSERT_NOT_NULL(doc);
yyjson_val *root = yyjson_doc_get_root(doc);
const char *source = yyjson_get_str(yyjson_obj_get(root, "source"));
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(root, "content_verified")));
ASSERT_NULL(strstr(source, "Intruder"));
ASSERT_NOT_NULL(strstr(source, "func ProcessOrder"));
yyjson_doc_free(doc);
free(inner);
/* The write did land: the next call sees it and withholds the source. */
inner = snippet_process_order(srv, &doc);
ASSERT_NOT_NULL(doc);
root = yyjson_doc_get_root(doc);
ASSERT_TRUE(yyjson_get_bool(yyjson_obj_get(root, "source_drift")));
yyjson_doc_free(doc);
free(inner);
cbm_mcp_server_free(srv);
cleanup_snippet_dir(tmp);
PASS();
}
#endif
/* #2585: metadata.generation names the coverage-recording run and does not
* change on incremental re-index, so it cannot tell two publications apart.
* store_generation does, and every response that carries it agrees. */
TEST(tool_responses_carry_store_generation_issue2585) {
char tmp[256];
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
ASSERT_NOT_NULL(srv);
cbm_store_t *store = cbm_mcp_server_store(srv);
ASSERT_NOT_NULL(store);
ASSERT_EQ(write_coverage_meta(store, "coverage-run-1", "complete"), CBM_STORE_OK);
ASSERT_EQ(record_main_go_hash(store, tmp, true, true), CBM_STORE_OK);
char before[96];
ASSERT_EQ(cbm_store_generation(store, before, sizeof(before)), CBM_STORE_OK);
yyjson_doc *cdoc = NULL;
char *cinner = NULL;
ASSERT_NOT_NULL(coverage_first_path(srv, &cdoc, &cinner));
yyjson_val *meta = yyjson_obj_get(yyjson_doc_get_root(cdoc), "metadata");
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(meta, "store_generation")), before);
yyjson_doc *sdoc = NULL;
char *sinner = snippet_process_order(srv, &sdoc);
ASSERT_NOT_NULL(sdoc);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(yyjson_doc_get_root(sdoc), "store_generation")),
before);
yyjson_doc_free(cdoc);
free(cinner);
yyjson_doc_free(sdoc);
free(sinner);
/* An index run advances the store generation, not the coverage one. */
char project_dir[512];
snprintf(project_dir, sizeof(project_dir), "%s/project", tmp);
ASSERT_EQ(cbm_store_upsert_project(store, "test-project", project_dir), CBM_STORE_OK);
char after[96];
ASSERT_EQ(cbm_store_generation(store, after, sizeof(after)), CBM_STORE_OK);
ASSERT_STR_NEQ(after, before);
ASSERT_NOT_NULL(coverage_first_path(srv, &cdoc, &cinner));
meta = yyjson_obj_get(yyjson_doc_get_root(cdoc), "metadata");
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(meta, "store_generation")), after);
ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(meta, "generation")), "coverage-run-1");
yyjson_doc_free(cdoc);
free(cinner);
cbm_mcp_server_free(srv);
cleanup_snippet_dir(tmp);
PASS();
}
TEST(tool_check_index_coverage_surfaces_lookup_errors) {
char tmp[256];
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
@@ -25957,6 +26247,12 @@ SUITE(mcp) {
RUN_TEST(tool_check_index_coverage_rejects_stale_generation);
RUN_TEST(tool_check_index_coverage_freshness_uses_indexer_mtime_source_issue1714);
RUN_TEST(tool_check_index_coverage_requires_source_when_file_metadata_changed);
RUN_TEST(tool_check_index_coverage_freshness_verifies_content_issue2585);
RUN_TEST(tool_get_code_snippet_withholds_source_whose_content_drifted_issue2585);
#ifdef CBM_ENABLE_TEST_SEAMS
RUN_TEST(tool_get_code_snippet_serves_the_bytes_it_verified_issue2585);
#endif
RUN_TEST(tool_responses_carry_store_generation_issue2585);
RUN_TEST(tool_check_index_coverage_surfaces_lookup_errors);
RUN_TEST(tool_index_status_includes_git_metadata);
+211 -15
View File
@@ -9743,6 +9743,104 @@ TEST(pipeline_ts_crossfile_new_instance_method_call_issue1354) {
PASS();
}
/* Issue #2297: a call through `import { foo as fooShared }` produced no CALLS
* edge, so trace_path(foo, inbound) missed the caller. End to end through the
* real pipeline: the renamed call, an app-local wrapper that shares the
* exported name, and a method on a renamed class import all reach the
* exported member, while the plain named import control keeps lsp_ts_import. */
TEST(pipeline_ts_renamed_import_call_issue2297) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_ts_2297_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_temp_file(tmp, "src/lib.ts",
"export function fooUniqueQrs(x: number): number {\n"
" return x + 1;\n"
"}\n"
"export function buildHealthUniqueQrs(input: { a: number }): number {\n"
" return input.a;\n"
"}\n"
"export class ConnUniqueQrs {\n"
" pingUniqueQrs(): number {\n"
" return 1;\n"
" }\n"
"}\n");
write_temp_file(tmp, "src/plain.ts",
"import { fooUniqueQrs } from './lib';\n"
"export function callsPlain(): number {\n"
" return fooUniqueQrs(1);\n"
"}\n");
write_temp_file(tmp, "src/renamed.ts",
"import { fooUniqueQrs as fooShared } from './lib';\n"
"export function callsRenamed(): number {\n"
" return fooShared(1);\n"
"}\n");
write_temp_file(tmp, "src/wrapper.ts",
"import { buildHealthUniqueQrs as buildShared } from './lib';\n"
"export function buildHealthUniqueQrs(input: { a: number }): number {\n"
" return buildShared({ ...input });\n"
"}\n");
write_temp_file(tmp, "src/klass.ts",
"import { ConnUniqueQrs as Connection } from './lib';\n"
"export function viaRenamedClass(): number {\n"
" return new Connection().pingUniqueQrs();\n"
"}\n");
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/ts_2297.db", tmp);
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);
const char *project = cbm_pipeline_project_name(p);
cbm_store_t *s = cbm_store_open_path(db_path);
ASSERT_NOT_NULL(s);
bool plain =
cross_file_call_has_strategy(s, project, "callsPlain", "fooUniqueQrs", "lsp_ts_import");
bool renamed = cross_file_call_has_strategy(s, project, "callsRenamed", "fooUniqueQrs",
"lsp_import_alias");
char wrapper_qn[512];
char exported_qn[512];
snprintf(wrapper_qn, sizeof(wrapper_qn), "%s.src.wrapper.buildHealthUniqueQrs", project);
snprintf(exported_qn, sizeof(exported_qn), "%s.src.lib.buildHealthUniqueQrs", project);
cbm_node_t wrapper_node = {0};
cbm_node_t exported_node = {0};
bool wrapper = false;
bool recursive = false;
if (cbm_store_find_node_by_qn(s, project, wrapper_qn, &wrapper_node) == CBM_STORE_OK &&
cbm_store_find_node_by_qn(s, project, exported_qn, &exported_node) == CBM_STORE_OK) {
cbm_edge_t *edges = NULL;
int edge_count = 0;
cbm_store_find_edges_by_source_type(s, wrapper_node.id, "CALLS", &edges, &edge_count);
for (int i = 0; i < edge_count; i++) {
if (edges[i].target_id == exported_node.id && edges[i].properties_json &&
strstr(edges[i].properties_json, "lsp_import_alias"))
wrapper = true;
if (edges[i].target_id == wrapper_node.id)
recursive = true;
}
cbm_store_free_edges(edges, edge_count);
}
cbm_node_free_fields(&wrapper_node);
cbm_node_free_fields(&exported_node);
bool renamed_class = cross_file_call_has_strategy(s, project, "viaRenamedClass",
"pingUniqueQrs", "lsp_ts_method");
cbm_store_close(s);
cbm_pipeline_free(p);
th_rmtree(tmp);
ASSERT_TRUE(plain);
ASSERT_TRUE(renamed);
ASSERT_TRUE(wrapper);
ASSERT_FALSE(recursive);
ASSERT_TRUE(renamed_class);
PASS();
}
/* A query failure is distinct from a valid zero-edge result. */
static int ts514_pipeline_count(cbm_store_t *store, const char *sql, const char **values,
int value_count) {
@@ -10931,6 +11029,101 @@ TEST(pipeline_go_multi_return_receiver_is_typed_parallel) {
return go_multi_return_case(52);
}
/* #2033: an identity alias declared in one file is followed from another file
* of the same package. `type FeatureSet = bitmap` (featureset.go), the method
* `bitmap.Set` (bitmap.go) and the call `reqs.Set(3)` (framework.go) are three
* files, so the alias reaches the call only through the cross-file defs. The
* same-package decoy `other.Set` means the call cannot bind by name. Before the
* fix the call had no CALLS edge at all. pad_files > 0 forces the parallel
* resolver and its prebuilt registry. */
static int go_crossfile_type_alias_case(int pad_files) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_alias_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_temp_file(tmp, "go.mod", "module example.com/fx\n\ngo 1.22\n");
write_temp_file(tmp, "fs/bitmap.go",
"package fs\n"
"\n"
"type bitmap []uint64\n"
"\n"
"func (b bitmap) Set(i int) { b[i/64] |= 1 << (i % 64) }\n");
write_temp_file(tmp, "fs/featureset.go",
"package fs\n"
"\n"
"type FeatureSet = bitmap\n");
write_temp_file(
tmp, "fs/framework.go",
"package fs\n"
"\n"
"type Framework struct{}\n"
"\n"
"func (f *Framework) NewFeatureSet() FeatureSet { return make(FeatureSet, 1) }\n"
"\n"
"func Use(f *Framework) {\n"
"\treqs := f.NewFeatureSet()\n"
"\treqs.Set(3)\n"
"}\n");
write_temp_file(tmp, "fs/other.go",
"package fs\n"
"\n"
"type other struct{}\n"
"\n"
"func (o other) Set(i int) {}\n");
for (int i = 0; i < pad_files; i++) {
char name[64];
char body[128];
snprintf(name, sizeof(name), "filler/filler%d.go", i);
snprintf(body, sizeof(body), "package filler\n\nfunc Filler%d() int { return %d }\n", i, i);
write_temp_file(tmp, name, body);
}
char *saved = NULL;
if (pad_files > 0) {
char *old_workers = getenv("CBM_WORKERS");
saved = old_workers ? strdup(old_workers) : NULL;
cbm_setenv("CBM_WORKERS", "4", 1);
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/go_alias.db", tmp);
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
int rc = p ? cbm_pipeline_run(p) : -1;
if (pad_files > 0) {
if (saved) {
cbm_setenv("CBM_WORKERS", saved, 1);
free(saved);
} else {
cbm_unsetenv("CBM_WORKERS");
}
}
ASSERT_NOT_NULL(p);
ASSERT_EQ(rc, 0);
const char *project = cbm_pipeline_project_name(p);
cbm_store_t *s = cbm_store_open_path(db_path);
ASSERT_NOT_NULL(s);
int right = named_edge_to_file_count(s, project, "CALLS", "Use", "Set", "fs/bitmap.go");
int decoy = named_edge_to_file_count(s, project, "CALLS", "Use", "Set", "fs/other.go");
bool typed = cross_file_call_has_strategy(s, project, "Use", "Set", "\"strategy\":\"lsp_");
if (right != 1 || decoy != 0 || !typed) {
printf(" Use -> Set: right=%d decoy=%d lsp=%d\n", right, decoy, typed);
FAIL("call through a cross-file type alias not bound to the aliased type's method");
}
cbm_store_close(s);
cbm_pipeline_free(p);
th_rmtree(tmp);
PASS();
}
TEST(pipeline_go_crossfile_type_alias_method) {
return go_crossfile_type_alias_case(0);
}
TEST(pipeline_go_crossfile_type_alias_method_parallel) {
return go_crossfile_type_alias_case(52);
}
TEST(pipeline_go_rw_usage_never_cross_into_c) {
/* #1928: USAGE and WRITES/READS resolve through the same short-name
* registry as CALLS but never consulted the #725 cross-language guard.
@@ -26101,7 +26294,7 @@ TEST(pipeline_python_namespace_change_admission) {
}
int body = -9, signature = -9, alias = -9, order = -9;
int empty = -9, literal = -9, unknown = -9, deletion = -9, transition = -9;
const char *non_python = "{\"v\":3,\"lang\":0,\"lsp\":[],\"reg\":[],\"py\":null}";
const char *non_python = "{\"v\":4,\"lang\":0,\"lsp\":[],\"reg\":[],\"py\":null}";
if (complete) {
body = cbm_lsp_surface_python_change(json[0], json[1]);
signature = cbm_lsp_surface_python_change(json[0], json[2]);
@@ -26232,24 +26425,24 @@ TEST(pipeline_python_namespace_codec_rejects_non_authoritative_rows) {
* it re-extracts in full. */
const char *bad[] = {
"{\"v\":1,\"lsp\":[]}",
"{\"v\":3,\"lang\":1,\"lsp\":[]}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":null}",
"{\"v\":3,\"lang\":1.0,\"lsp\":[],\"py\":null}",
"{\"v\":3,\"v\":3,\"lang\":0,\"lsp\":[],\"py\":null}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[]}",
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":null}",
"{\"v\":4,\"lang\":1.0,\"lsp\":[],\"py\":null}",
"{\"v\":4,\"v\":4,\"lang\":0,\"lsp\":[],\"py\":null}",
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":3,\"failure\":2,\"events\":[]}}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":1,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":[]}}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":99,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":99,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":[]}}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\\u0000suffix\",\"status\":2,"
"\"failure\":0,\"events\":[]}}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
"{\"k\":999,\"d\":0,\"s\":0,\"r\":0,\"f\":0,\"l\":0,"
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}",
"{\"v\":3,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"py\":{\"v\":2,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
"{\"k\":8,\"d\":0,\"s\":1,\"r\":0,\"f\":0,\"l\":4294967296,"
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}",
@@ -26269,7 +26462,7 @@ TEST(pipeline_python_namespace_codec_rejects_non_authoritative_rows) {
cbm_arena_destroy(&arena);
}
const char *unknown =
"{\"v\":3,\"lang\":1,\"lsp\":[],\"reg\":[],\"py\":{\"v\":2,\"lang\":1,"
"{\"v\":4,\"lang\":1,\"lsp\":[],\"reg\":[],\"py\":{\"v\":2,\"lang\":1,"
"\"module\":\"p.m\",\"path\":\"m.py\",\"status\":2,\"failure\":0,\"events\":["
"{\"k\":7,\"d\":0,\"s\":0,\"r\":7,\"f\":0,\"l\":0,"
"\"n\":null,\"m\":null,\"i\":null,\"a\":[]}]}}";
@@ -26297,7 +26490,7 @@ TEST(pipeline_lsp_surface_large_object_array_decode) {
const size_t capacity = (size_t)count * 192 + 64;
char *json = malloc(capacity);
ASSERT_NOT_NULL(json);
size_t used = (size_t)snprintf(json, capacity, "{\"v\":3,\"lang\":0,\"py\":null,\"lsp\":[");
size_t used = (size_t)snprintf(json, capacity, "{\"v\":4,\"lang\":0,\"py\":null,\"lsp\":[");
for (int i = 0; i < count; i++) {
int n = snprintf(json + used, capacity - used,
"%s{\"qn\":\"pkg.f%d\",\"sn\":\"f%d\",\"lb\":\"Function\","
@@ -26387,10 +26580,10 @@ TEST(pipeline_lsp_surface_persisted_and_body_edit_invariant) {
ASSERT_TRUE(row_a->surface_sha && row_a->surface_sha[0]);
if (round == 0) {
snprintf(sha_baseline, sizeof(sha_baseline), "%s", row_a->surface_sha);
/* The row is the versioned codec envelope (codec version 3) with
/* The row is the versioned codec envelope (codec version 4) with
* the def present. */
snprintf(json_probe, sizeof(json_probe), "%.20s", row_a->defs_json);
ASSERT_TRUE(strstr(row_a->defs_json, "\"v\":3") != NULL);
ASSERT_TRUE(strstr(row_a->defs_json, "\"v\":4") != NULL);
ASSERT_TRUE(strstr(row_a->defs_json, "surface_probe") != NULL);
} else if (round == 1) {
ASSERT_STR_EQ(row_a->surface_sha, sha_baseline);
@@ -28952,6 +29145,7 @@ SUITE(pipeline) {
RUN_TEST(pipeline_tsjs_receiver_suppresses_weak_method_edge);
RUN_TEST(pipeline_axios_wrapper_baseurl_composes_http_calls_issue1916);
RUN_TEST(pipeline_ts_crossfile_new_instance_method_call_issue1354);
RUN_TEST(pipeline_ts_renamed_import_call_issue2297);
RUN_TEST(pipeline_ts_param_property_injection_issue514);
RUN_TEST(pipeline_python_receiver_suppresses_weak_method_edge);
RUN_TEST(pipeline_php_receiver_suppresses_weak_method_edge);
@@ -28963,6 +29157,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_go_receiver_parallel_suppresses_weak_method_edge);
RUN_TEST(pipeline_go_multi_return_receiver_is_typed);
RUN_TEST(pipeline_go_multi_return_receiver_is_typed_parallel);
RUN_TEST(pipeline_go_crossfile_type_alias_method);
RUN_TEST(pipeline_go_crossfile_type_alias_method_parallel);
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c_parallel);
RUN_TEST(pipeline_rust_call_never_binds_another_language);
+10 -2
View File
@@ -947,8 +947,8 @@ TEST(platform_env_dup_honours_the_settings_resolver) {
* setter must update that same wide environment; _putenv_s alone interprets
* UTF-8 path bytes through the active ANSI code page. */
TEST(platform_setenv_preserves_utf8_in_wide_environment) {
static const char utf8[] = "C:/cbm-cache-\xce\x94-\xe6\x97\xa5\xe6\x9c\xac";
static const wchar_t wide[] = L"C:/cbm-cache-\u0394-\u65e5\u672c";
static const char utf8[] = "C:/cbm-cache-\xce\x94-\xe6\x97\xa5\xe6\x9c\xac-\xe4\xb8\x81";
static const wchar_t wide[] = L"C:/cbm-cache-\u0394-\u65e5\u672c-\u4e01";
ASSERT_EQ(cbm_setenv("CBM_CACHE_DIR", utf8, 1), 0);
wchar_t observed_wide[128];
ASSERT_EQ(GetEnvironmentVariableW(L"CBM_CACHE_DIR", observed_wide, 128), (DWORD)(wcslen(wide)));
@@ -956,6 +956,14 @@ TEST(platform_setenv_preserves_utf8_in_wide_environment) {
char observed_utf8[128];
ASSERT_NOT_NULL(cbm_safe_getenv("CBM_CACHE_DIR", observed_utf8, sizeof(observed_utf8), NULL));
ASSERT_STR_EQ(observed_utf8, utf8);
/* Narrow getenv must agree with the CRT's active ANSI code page. */
char expected_narrow[128];
ASSERT_GT(WideCharToMultiByte(CP_ACP, 0, wide, -1, expected_narrow,
(int)sizeof(expected_narrow), NULL, NULL),
0);
const char *observed_narrow = getenv("CBM_CACHE_DIR");
ASSERT_NOT_NULL(observed_narrow);
ASSERT_STR_EQ(observed_narrow, expected_narrow);
(void)cbm_unsetenv("CBM_CACHE_DIR");
PASS();
}
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env bash
# Contract: the PRODUCTION allocator object ($BUILD_DIR/prod_mimalloc.o, mimalloc's
# release build with cbm's override defines) keeps three behaviours that no unit
# suite can see, because the test runner links mimalloc's debug build with no
# global override (MIMALLOC_CFLAGS_TEST in Makefile.cbm).
#
# 1. free(NULL) before mimalloc's constructor is a no-op (Linux / ELF only).
# glibc 2.44's newlocale() frees NULL during libstdc++ start-up, before the
# allocator's constructor has run. mimalloc 3.3.2's release build read its
# not-yet-created page map there and every binary died with SIGSEGV at
# start-up (mimalloc #1341, fixed upstream by b2226c19, first released in
# v3.4.4). An ELF .preinit_array hook reproduces it on ANY libc, because it
# runs before every constructor. Reported with this exact reproduction by
# Felix Rath in PR #2562.
# 2. The version banner carries no build clock. Up to mimalloc 3.4.4 that took
# a local patch to src/options.c; upstream dropped __DATE__/__TIME__ in
# v3.4.5 (mimalloc #1355), so it is now upstream behaviour that a refresh
# must keep. prod_mimalloc.o is compiled with -w and without
# CFLAGS_COMMON, so -Wdate-time does NOT guard the vendored sources; this
# fixture does. The same banner also shows the object was built with
# MI_PROFILE=0 (Makefile.cbm): a profiling build prints a "profiling" line.
# 3. macOS always takes the recommit path on decommit (local patch in
# vendored/mimalloc/src/prim/unix/prim.c, 63fb7b3d, see
# vendored/mimalloc/PATCHES.md): needs_recommit is true on Apple and stays
# upstream's false elsewhere.
#
# Each fixture also proves it linked the object it claims to test (an
# allocator-owned pointer from malloc on Linux, "debug level : 0" in the banner),
# so an accidental link against a different allocator fails instead of passing.
#
# Usage (after `make -f Makefile.cbm cbm`): bash tests/test_prod_allocator_contract.sh [build-dir]
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUILD_DIR="${1:-$ROOT/build/c}"
OBJ="$BUILD_DIR/prod_mimalloc.o"
case "$(uname -s)" in
Linux) PLATFORM=linux ;;
Darwin) PLATFORM=macos ;;
*)
# WHY skipped: on Windows the production allocator is mimalloc's
# static-CRT override, linked with --wrap flags this fixture does not
# reproduce; ELF preinit and the unix decommit path do not exist there.
# The banner patch is platform-independent source and is covered by the
# Linux and macOS legs.
echo "SKIP_PLATFORM: production allocator contract runs on Linux and macOS"
exit 0
;;
esac
if [[ ! -f "$OBJ" ]]; then
echo "FAIL: production allocator object missing: $OBJ (build cbm first)" >&2
exit 1
fi
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
read -r -a CC_CMD <<< "${CC:-cc}"
build_fixture() {
local name="$1"
"${CC_CMD[@]}" -std=gnu11 -O2 -Wall -Wextra -Werror \
-I"$ROOT/vendored/mimalloc/include" "$WORK/$name.c" "$OBJ" \
-lm -lpthread -o "$WORK/$name"
}
run_fixture() {
local name="$1" what="$2" status=0
"$WORK/$name" || status=$?
if [[ "$status" -ne 0 ]]; then
echo "FAIL: $what (exit $status)" >&2
exit 1
fi
echo "PASS: $what"
}
# --- 1. free(NULL) before allocator initialization (ELF preinit) -------------
if [[ "$PLATFORM" == linux ]]; then
cat > "$WORK/preinit_free.c" <<'EOF'
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "mimalloc.h"
static volatile bool early_free_ran;
static void early_free(void) {
void (*volatile release)(void *) = free; /* keep the call: free(NULL) is foldable */
release(NULL);
early_free_ran = true;
}
__attribute__((section(".preinit_array"), used))
static void (*const cbm_preinit_free)(void) = early_free;
int main(void) {
if (!early_free_ran) {
return 2; /* the hook never ran: the fixture proves nothing */
}
void *p = malloc(64);
if (p == NULL) {
return 3;
}
memset(p, 0x5A, 64); /* GCC warns on passing unwritten heap memory as const */
if (!mi_is_in_heap_region(p)) {
return 4; /* malloc is not mimalloc: the override was not linked */
}
free(p);
return 0;
}
EOF
build_fixture preinit_free
run_fixture preinit_free "free(NULL) before allocator initialization (mimalloc #1341)"
fi
# --- 2. version banner without __DATE__/__TIME__ -----------------------------
cat > "$WORK/banner.c" <<'EOF'
#include <stdio.h>
#include <string.h>
#include "mimalloc.h"
static char out[16384];
static size_t out_len;
static void capture(const char *msg, void *arg) {
(void)arg;
size_t n = strlen(msg);
if (out_len + n < sizeof(out)) {
memcpy(out + out_len, msg, n);
out_len += n;
out[out_len] = '\0';
}
}
int main(void) {
mi_options_print_out(capture, NULL);
int v = mi_version();
char want[64];
snprintf(want, sizeof(want), "v%d.%d.%d\n", v / 10000, (v % 10000) / 100, v % 100);
if (strncmp(out, want, strlen(want)) != 0) {
fprintf(stderr, "banner first line is not exactly %s", want);
fprintf(stderr, "got: %.120s\n", out);
return 1;
}
if (strstr(out, "built on") != NULL) {
fprintf(stderr, "banner carries the build clock\n");
return 1;
}
if (strstr(out, "debug level : 0\n") == NULL) {
fprintf(stderr, "not the release allocator (MI_DEBUG=0) the product links\n");
return 1;
}
if (strstr(out, "\nprofiling") != NULL) {
fprintf(stderr, "allocator built with its sampling profiler (production sets MI_PROFILE=0)\n");
return 1;
}
return 0;
}
EOF
build_fixture banner
run_fixture banner "allocator banner is reproducible (no __DATE__/__TIME__) and MI_PROFILE=0"
# --- 3. decommit: macOS recommits, other platforms keep upstream -------------
cat > "$WORK/decommit.c" <<'EOF'
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
/* mimalloc-internal primitive (include/mimalloc/prim.h); hidden visibility still
* links statically. */
int _mi_prim_decommit(void *addr, size_t size, bool *needs_recommit);
int main(void) {
#if defined(__APPLE__)
const bool want = true;
#else
const bool want = false;
#endif
const size_t size = (size_t)1 << 20;
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (p == MAP_FAILED) {
return 3;
}
memset(p, 0xA5, size);
bool needs_recommit = !want; /* the call must overwrite it */
if (_mi_prim_decommit(p, size, &needs_recommit) != 0) {
return 4;
}
if (needs_recommit != want) {
fprintf(stderr, "needs_recommit=%d, expected %d\n", (int)needs_recommit, (int)want);
return 1;
}
return 0;
}
EOF
build_fixture decommit
if [[ "$PLATFORM" == macos ]]; then
run_fixture decommit "decommit takes the recommit path on macOS (local patch 63fb7b3d)"
else
run_fixture decommit "decommit keeps upstream's release behaviour off macOS"
fi
+251
View File
@@ -2787,6 +2787,125 @@ TEST(tslsp_crossfile_chain_through_return) {
PASS();
}
/* The call target and its return signature must use the same exported name,
* even when the module also exports a function named like the local alias. */
static int ts_renamed_import_return_chain(bool shared_registry) {
const char *source = "import { make as alias } from './lib';\n"
"function go() { alias().read(); }\n";
CBMLSPDef defs[] = {
{.qualified_name = "test.main.go",
.short_name = "go",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.main"},
{.qualified_name = "test.lib.Good",
.short_name = "Good",
.label = "Class",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.method_names_str = "read"},
{.qualified_name = "test.lib.Good.read",
.short_name = "read",
.label = "Method",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.receiver_type = "test.lib.Good"},
{.qualified_name = "test.lib.Bad",
.short_name = "Bad",
.label = "Class",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.method_names_str = "read"},
{.qualified_name = "test.lib.Bad.read",
.short_name = "read",
.label = "Method",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.receiver_type = "test.lib.Bad"},
{.qualified_name = "test.lib.make",
.short_name = "make",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.return_types = "test.lib.Good"},
{.qualified_name = "test.lib.alias",
.short_name = "alias",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.return_types = "test.lib.Bad"},
};
enum { NDEFS = (int)(sizeof(defs) / sizeof(defs[0])) };
const char *imp_names[] = {"alias"};
const char *imp_qns[] = {"test.lib"};
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
if (shared_registry) {
CBMTypeRegistry *reg = cbm_ts_build_cross_registry(&arena, defs, NDEFS);
ASSERT_NOT_NULL(reg);
cbm_run_ts_lsp_cross_with_registry(&arena, source, (int)strlen(source), "test.main", false,
false, false, reg, defs, NDEFS, imp_names, imp_qns, 1,
NULL, &out);
} else {
cbm_run_ts_lsp_cross(&arena, source, (int)strlen(source), "test.main", false, false, false,
defs, NDEFS, imp_names, imp_qns, 1, NULL, &out);
}
bool direct = has_exact_resolved(&out, "test.main.go", "test.lib.make", "lsp_import_alias");
bool good = has_exact_resolved(&out, "test.main.go", "test.lib.Good.read", "lsp_ts_method");
bool bad = has_exact_resolved(&out, "test.main.go", "test.lib.Bad.read", "lsp_ts_method");
cbm_arena_destroy(&arena);
ASSERT_TRUE(direct);
ASSERT_FALSE(bad);
ASSERT_TRUE(good);
PASS();
}
TEST(tslsp_crossfile_renamed_import_return_chain) {
return ts_renamed_import_return_chain(false);
}
TEST(tslsp_crossfile_renamed_import_return_chain_shared) {
return ts_renamed_import_return_chain(true);
}
/* Keep the supplied import map at one entry: this measures the AST alias
* collector independently of the older import-map insertion path. */
static size_t ts_import_alias_arena_bytes(int count) {
size_t capacity = (size_t)count * 48 + 64;
char *source = malloc(capacity);
if (!source)
return 0;
size_t len = (size_t)snprintf(source, capacity, "import { ");
for (int i = 0; i < count; i++) {
len += (size_t)snprintf(source + len, capacity - len, "%sf%d as a%d", i ? ", " : "", i, i);
}
len += (size_t)snprintf(source + len, capacity - len, " } from './lib';\n");
const char *imp_names[] = {"a0"};
const char *imp_qns[] = {"test.lib"};
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
cbm_run_ts_lsp_cross(&arena, source, (int)len, "test.main", false, false, false, NULL, 0,
imp_names, imp_qns, 1, NULL, &out);
size_t bytes = cbm_arena_total(&arena);
cbm_arena_destroy(&arena);
free(source);
return bytes;
}
TEST(tslsp_import_alias_arena_growth) {
size_t small = ts_import_alias_arena_bytes(1024);
size_t large = ts_import_alias_arena_bytes(2048);
ASSERT_GT(small, 0);
ASSERT_GT(large, small);
printf(" aliases arena bytes: 1024=%zu 2048=%zu ", small, large);
/* Linear source/AST work plus geometric storage stays below 3x; retaining
* two complete arrays at every insertion grows approximately 4x. */
ASSERT(large < small * 3);
PASS();
}
TEST(tslsp_crossfile_no_def_match) {
/* Function that imports a symbol not in the def list — should resolve safely
* (emit unresolved, no crash). */
@@ -2815,6 +2934,132 @@ TEST(tslsp_crossfile_no_def_match) {
PASS();
}
/* Issue #2297: `import { foo as fooShared }` bound only the local alias, and
* every import lookup spelled `<module>.<alias>`, which names nothing, so a call
* through a renamed import produced no CALLS edge. The binding resolves to the
* name the module exports, exactly like a plain named import. Driven through
* the production Tier-2 path (shared sealed registry + per-file overlay). */
TEST(tslsp_crossfile_renamed_import_issue2297) {
CBMLSPDef defs[] = {
{.qualified_name = "test.lib.foo",
.short_name = "foo",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.lib",
.return_types = "number"},
{.qualified_name = "test.conn.Conn",
.short_name = "Conn",
.label = "Class",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.conn",
.method_names_str = "ping"},
{.qualified_name = "test.conn.Conn.ping",
.short_name = "ping",
.label = "Method",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.conn",
.receiver_type = "test.conn.Conn"},
{.qualified_name = "test.main.callsRenamed",
.short_name = "callsRenamed",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.main"},
{.qualified_name = "test.main.viaRenamedClass",
.short_name = "viaRenamedClass",
.label = "Function",
.lang = CBM_LANG_TYPESCRIPT,
.def_module_qn = "test.main"},
};
enum { NDEFS = (int)(sizeof(defs) / sizeof(defs[0])) };
const char *imp_names[] = {"fooShared", "Connection"};
const char *imp_qns[] = {"test.lib", "test.conn"};
CBMArena arena;
cbm_arena_init(&arena);
CBMTypeRegistry *reg = cbm_ts_build_cross_registry(&arena, defs, NDEFS);
ASSERT_NOT_NULL(reg);
const char *src = "import { foo as fooShared } from './lib';\n"
"import { Conn as Connection } from './conn';\n"
"export function callsRenamed() { return fooShared(1); }\n"
"export function viaRenamedClass() { const c = new Connection(); c.ping(); }\n";
CBMResolvedCallArray out = {0};
cbm_run_ts_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.main", false, false,
false, reg, defs, NDEFS, imp_names, imp_qns, 2, NULL, &out);
/* The call site names the alias, so the join reads it from `reason`. */
ASSERT_TRUE(
has_exact_resolved(&out, "test.main.callsRenamed", "test.lib.foo", "lsp_import_alias"));
ASSERT_TRUE(has_exact_resolved(&out, "test.main.viaRenamedClass", "test.conn.Conn.ping",
"lsp_ts_method"));
cbm_arena_destroy(&arena);
PASS();
}
/* The export name is looked up exactly. A renamed import of a name the module
* does not export must not fall back to the alias: before #2297 the alias `foo`
* spelled `test.lib.foo` and bound this call to an unrelated export. */
TEST(tslsp_crossfile_renamed_import_missing_export_issue2297) {
const char *source = "import { missing as foo } from './lib';\n"
"function go() { foo(1); }\n";
CBMLSPDef defs[] = {
{.qualified_name = "test.main.go",
.short_name = "go",
.label = "Function",
.def_module_qn = "test.main"},
{.qualified_name = "test.lib.foo",
.short_name = "foo",
.label = "Function",
.def_module_qn = "test.lib"},
};
const char *imp_names[] = {"foo"};
const char *imp_qns[] = {"test.lib"};
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
cbm_run_ts_lsp_cross(&arena, source, (int)strlen(source), "test.main", false, false, false,
defs, 2, imp_names, imp_qns, 1, NULL, &out);
ASSERT_EQ(find_resolved_arr(&out, "go", "test.lib.foo"), -1);
cbm_arena_destroy(&arena);
PASS();
}
/* `import { default as greet }` is a default import: it keeps resolving by its
* local name, as `import greet from` does. */
TEST(tslsp_crossfile_default_as_import_issue2297) {
const char *source = "import { default as greet } from './greetings';\n"
"function go() { greet('hi'); }\n";
CBMLSPDef defs[] = {
{.qualified_name = "test.main.go",
.short_name = "go",
.label = "Function",
.def_module_qn = "test.main"},
{.qualified_name = "test.greetings.greet",
.short_name = "greet",
.label = "Function",
.def_module_qn = "test.greetings",
.return_types = "string"},
};
const char *imp_names[] = {"greet"};
const char *imp_qns[] = {"test.greetings"};
CBMArena arena;
cbm_arena_init(&arena);
CBMResolvedCallArray out = {0};
cbm_run_ts_lsp_cross(&arena, source, (int)strlen(source), "test.main", false, false, false,
defs, 2, imp_names, imp_qns, 1, NULL, &out);
ASSERT_GTE(find_resolved_arr(&out, "go", "test.greetings.greet"), 0);
cbm_arena_destroy(&arena);
PASS();
}
/* ── More stdlib (Date, RegExp, Error, Math) ──────────────────────────────── */
TEST(tslsp_stdlib_date_toISOString) {
@@ -5243,7 +5488,13 @@ SUITE(ts_lsp) {
RUN_TEST(tslsp_scale_many_defs_no_crash_issue344);
RUN_TEST(tslsp_crossfile_function_call);
RUN_TEST(tslsp_crossfile_chain_through_return);
RUN_TEST(tslsp_crossfile_renamed_import_return_chain);
RUN_TEST(tslsp_crossfile_renamed_import_return_chain_shared);
RUN_TEST(tslsp_import_alias_arena_growth);
RUN_TEST(tslsp_crossfile_no_def_match);
RUN_TEST(tslsp_crossfile_renamed_import_issue2297);
RUN_TEST(tslsp_crossfile_renamed_import_missing_export_issue2297);
RUN_TEST(tslsp_crossfile_default_as_import_issue2297);
/* More stdlib */
RUN_TEST(tslsp_stdlib_date_toISOString);
+95
View File
@@ -0,0 +1,95 @@
# Vendored mimalloc: source pin and local patches
## Source pin
- Upstream: <https://github.com/microsoft/mimalloc>
- Tag: `v3.5.3` (annotated tag object `deac3dcf5f0614884013d411a5c07e16bec96c9f`)
- Commit: `d4881d338125e1cb7c47ba4cfb398d6f7c0c8d45`
(tree `fbc4e8f72287d127c079ada5ede870b7d63e55f5`)
- Vendored: `LICENSE`, every file under `include/` and every file under `src/`.
Nothing else (no build files, tests or docs).
v3.5.3 is upstream's latest published release at the time of the pin
(GitHub release of 2026-09-17, on the `main3` branch).
v3.5.3 contains upstream commit `b2226c195fe884223f6d6f68ea52df476e57e5c2`
(mimalloc #1341, first released in v3.4.4): `free(NULL)` must work before the
allocator is initialized. With 3.3.2's release build, glibc 2.44's
`newlocale()` freed NULL during libstdc++ start-up, before mimalloc's
constructor, and every Linux binary died with SIGSEGV (reported with a
reproduction by Felix Rath in #2562). The preinit fixture in
`tests/test_prod_allocator_contract.sh` pins it.
## Verifying the vendored bytes
Every vendored file is byte-identical to the tag except the two hunks below.
Re-check from a fresh bare clone (nothing in it is built or run):
```sh
git clone --bare https://github.com/microsoft/mimalloc.git /tmp/mimalloc.git
git --git-dir=/tmp/mimalloc.git rev-parse 'v3.5.3^{commit}' # d4881d33...
for f in $(git --git-dir=/tmp/mimalloc.git ls-tree -r --name-only v3.5.3 -- LICENSE include src); do
[ "$(git --git-dir=/tmp/mimalloc.git rev-parse "v3.5.3:$f")" = \
"$(git hash-object "vendored/mimalloc/$f")" ] || echo "differs: $f"
done
```
The expected output is exactly the two files named below, and
`vendored/mimalloc/include` plus `vendored/mimalloc/src` must hold no file the
tag lacks.
## Local patches (reapply on every refresh)
Each patch is pinned by a check that fails when the patch is dropped.
### 1. macOS decommit always recommits: `src/prim/unix/prim.c`
In `_mi_prim_decommit`, macOS takes the recommit path that upstream reserves for
`MI_DEBUG` / `MI_SECURE>2` builds, so a purged range is not re-accounted as a
whole by `MADV_FREE_REUSE` when the arena reuses it. This cut the indexer's peak
`phys_footprint` substantially (measurements in the inline comment and in commit
63fb7b3d; re-measured on v3.5.3 when this pin was taken). Linux and Windows keep
upstream's behaviour.
Pinned by: the decommit fixture in `tests/test_prod_allocator_contract.sh`
(`needs_recommit` must be true on macOS and upstream's false elsewhere).
### 2. Comment wording: `src/prim/osx/alloc-override-zone.c`
Comment only, no code change: "crash in fork()." reads "crash while forking.".
`scripts/security-vendored.sh` rejects call spellings such as `fork(` anywhere
in vendored C, comments included, so the upstream sentence fails that gate.
(Wording first proposed in #2562.)
Pinned by: `scripts/security-vendored.sh` itself, which reports the pristine
upstream line as a blocked subprocess call.
## Build configuration
`Makefile.cbm` compiles the production object with `-DMI_PROFILE=0`, which
leaves out mimalloc's allocation-sampling profiler (on by default since 3.5.2)
and its sample countdown on the allocation path. Pinned by: the banner fixture
in `tests/test_prod_allocator_contract.sh` (a profiling build prints a
`profiling` line). The test build keeps upstream's default.
## Dropped patches
- Reproducible version banner (`src/options.c`, carried from 1f674c80 up to
3.4.4): upstream removed `__DATE__`/`__TIME__` from the banner in v3.4.5
(mimalloc #1355). The banner fixture in
`tests/test_prod_allocator_contract.sh` still asserts that the first line is
exactly `vX.Y.Z` with no build clock, so a future refresh cannot bring it
back unnoticed.
## Refresh procedure
1. Replace `LICENSE`, `include/` and `src/` with the new tag's files, deleting
files the tag no longer has, and verify the staged tree against the tag
before patching.
2. Reapply the patches above, or drop the ones upstream has absorbed (and move
them to "Dropped patches" with the upstream reference).
3. Update the pin in this file and `versionInfo` in
`scripts/ci/generate-sbom.py`, then run `scripts/security-vendored.sh --update`.
4. After `make -f Makefile.cbm cbm`, run
`bash tests/test_prod_allocator_contract.sh build/c` on Linux and macOS
(`scripts/test.sh` runs it on every CI leg), and `scripts/security-vendored.sh`.
@@ -37,7 +37,7 @@ terms of the MIT license. A copy of the license can be found in the file
void operator delete (void* p, const std::nothrow_t&) noexcept { mi_free(p); }
void operator delete[](void* p, const std::nothrow_t&) noexcept { mi_free(p); }
mi_decl_new(n) void* operator new(std::size_t n) noexcept(false) { return mi_new(n); }
mi_decl_new(n) void* operator new(std::size_t n) noexcept(false) { return mi_new(n); }
mi_decl_new(n) void* operator new[](std::size_t n) noexcept(false) { return mi_new(n); }
mi_decl_new_nothrow(n) void* operator new (std::size_t n, const std::nothrow_t& tag) noexcept { (void)(tag); return mi_new_nothrow(n); }
@@ -48,7 +48,7 @@ terms of the MIT license. A copy of the license can be found in the file
void operator delete[](void* p, std::size_t n) noexcept { mi_free_size(p,n); };
#endif
#if (__cplusplus > 201402L || defined(__cpp_aligned_new))
#if (__cplusplus > 201402L && defined(__cpp_aligned_new))
void operator delete (void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }
void operator delete[](void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast<size_t>(al)); }
void operator delete (void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast<size_t>(al)); };
@@ -0,0 +1,79 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2024-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#pragma once
#ifndef MIMALLOC_PROFILE_H
#define MIMALLOC_PROFILE_H
#include <mimalloc.h>
#include <stdbool.h> // bool
#include <stdint.h> // uint64_t
//---------------------------------------------------------------------------
// Initial support for profiling hooks; still experimental
//---------------------------------------------------------------------------
#define MI_PROFILE_SAMPLE_DATA_MAX_SIZE (1024)
// User defined profiler; see `test_profile.c` for an example.
typedef struct mi_profiler_s mi_profiler_t;
// Profiler data is stored together with each sampled allocation (unless the `on_free` field in the profiler is NULL.)
typedef struct mi_profiler_sample_data_s {
size_t user_data_size; // size of the user_data (should be the `mi_profiler_t.sample_data_size`)
void* user_data[1]; // default, but can be less or more (up to 1KiB), depending on `sample_data_size`
} mi_profiler_sample_data_t;
// Profiling callback invoked on each sampled allocation.
// If `profiler_data!=NULL` (i.e. when `on_free` is not NULL), then `profiler_data->requested_size == requested_size`.
typedef size_t (mi_cdecl mi_profiler_on_alloc_fun )(mi_profiler_t* profiler, mi_profiler_sample_data_t* profiler_data, void* ptr, size_t requested_size, size_t bytes_sample_rate, uint64_t bytes_since_last_sample, const mi_heap_t* heap);
// Profiling callback invoked on each sampled in-place re-allocation.
typedef size_t (mi_cdecl mi_profiler_on_realloc_inplace_fun)(mi_profiler_t* profiler, mi_profiler_sample_data_t* profiler_data, void* ptr, size_t old_size, const mi_heap_t* heap);
// Profiling callback invoked on a previously sampled allocation.
typedef void (mi_cdecl mi_profiler_on_free_fun )(mi_profiler_t* profiler, mi_profiler_sample_data_t* profiler_data, void* ptr, const mi_heap_t* heap);
// A profiler
// All fields are considered immutable such that they can be copied and accessed concurrently. All fields can be NULL/0.
struct mi_profiler_s {
void* reserved; // opaque; reserved by mimalloc
size_t sample_data_size; // size of required profiler data for each sampled allocation (or zero for no data)
size_t initial_sample_rate;// initial sample rate in bytes (set to at least 1 or higher) (can be adjusted by `on_alloc`)
mi_profiler_on_alloc_fun* on_alloc; // called on a sampled allocation (may be called concurrently)
mi_profiler_on_free_fun* on_free; // called on when previous sampled allocation is freed (may be called concurrently)
mi_profiler_on_realloc_inplace_fun* on_realloc_inplace; // (as yet unused) called on in-place reallocation of a previous sampled allocation (may be called concurrently)
// ... more user fields allowed
};
// Exported definitions
#ifdef __cplusplus
extern "C" {
#endif
// attach a profiler to a particular heap only.
mi_decl_export bool mi_heap_profile(mi_heap_t* heap, mi_profiler_t* profiler);
// disable profiling for a particular heap; useful for a heap that the profiler uses itself for metadata.
mi_decl_export void mi_heap_profile_disable(mi_heap_t* heap);
// attach a profiler to any (current and future) heaps in a sub-process (unless those heaps disabled profiling)
mi_decl_export bool mi_subproc_profile(mi_subproc_id_t subproc_id, mi_profiler_t* profiler);
// attach a profiler to any heaps in the main sub-process
mi_decl_export bool mi_profile(mi_profiler_t* profiler);
// start sampling
mi_decl_export bool mi_profiler_start(mi_profiler_t* profiler);
// end sampling
mi_decl_export bool mi_profiler_stop(mi_profiler_t* profiler);
#ifdef __cplusplus
}
#endif
#endif // MIMALLOC_PROFILE_H
+15 -4
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2024-2025, Microsoft Research, Daan Leijen
Copyright (c) 2024-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -22,7 +22,7 @@ terms of the MIT license. A copy of the license can be found in the file
#elif __cplusplus >= 201103L
#define mi_decl_align(a) alignas(a)
#else
#define mi_decl_align(a)
#define mi_decl_align(a) _Alignas(a)
#endif
@@ -49,7 +49,7 @@ typedef struct mi_stat_counter_s {
MI_STAT_COUNT(threads) /* number of threads */ \
MI_STAT_COUNT(malloc_normal) /* allocated bytes <= MI_LARGE_OBJ_SIZE_MAX */ \
MI_STAT_COUNT(malloc_huge) /* allocated bytes in huge pages */ \
MI_STAT_COUNT(malloc_requested) /* malloc requested bytes */ \
MI_STAT_COUNTER(malloc_requested) /* malloc requested bytes */ \
\
MI_STAT_COUNTER(mmap_calls) \
MI_STAT_COUNTER(commit_calls) \
@@ -75,11 +75,16 @@ typedef struct mi_stat_counter_s {
/* only on v3 */ \
MI_STAT_COUNT(heaps) \
MI_STAT_COUNT(theaps) \
MI_STAT_COUNT(pages_os_abandoned) /* pages in the os abandoned list (bad) */ \
MI_STAT_COUNT(pages_os_allocated) /* pages allocated outside arenas (bad) */ \
MI_STAT_COUNTER(pages_reclaim_on_alloc) \
MI_STAT_COUNTER(pages_reclaim_on_free) \
MI_STAT_COUNTER(pages_reabandon_full) \
MI_STAT_COUNTER(pages_unabandon_busy_wait) \
MI_STAT_COUNTER(heaps_delete_wait)
MI_STAT_COUNTER(heaps_delete_wait) \
MI_STAT_COUNTER(pages_stat_updates) /* calls to successful page_stat_update */ \
MI_STAT_COUNTER(pages_stat_update_count) /* total free/allocs */ \
MI_STAT_COUNTER(profile_samples) /* total sampled profiled allocations */ \
// Size bins for chunks
typedef enum mi_chunkbin_e {
@@ -161,6 +166,12 @@ mi_decl_export bool mi_subproc_stats_get_exclusive(mi_subproc_id_t subproc_id
mi_decl_export char* mi_stats_as_json(mi_stats_t* stats, size_t buf_size, char* buf) mi_attr_noexcept; // use mi_free to free the result if the input buf == NULL
mi_decl_export size_t mi_stats_get_bin_size(size_t bin) mi_attr_noexcept;
// get theap stats (only thread safe on theaps belonging to the calling thread)
mi_decl_export bool mi_theap_stats_get(mi_theap_t* theap, mi_stats_t* stats) mi_attr_noexcept;
// add the theap stats to the stats of the parent heap and clear the theap stats
mi_decl_export void mi_theap_stats_merge_to_heap(mi_theap_t* theap) mi_attr_noexcept;
#ifdef __cplusplus
}
#endif
+91 -19
View File
@@ -8,7 +8,7 @@ terms of the MIT license. A copy of the license can be found in the file
#ifndef MIMALLOC_H
#define MIMALLOC_H
#define MI_MALLOC_VERSION 30302 // major + 2 digits minor + 2 digits patch
#define MI_MALLOC_VERSION 30503 // major + 2 digits minor + 2 digits patch
// ------------------------------------------------------
// Compiler specific attributes
@@ -95,8 +95,9 @@ terms of the MIT license. A copy of the license can be found in the file
// Includes
// ------------------------------------------------------
#include <stddef.h> // size_t
#include <stddef.h> // size_t, wchar_t
#include <stdbool.h> // bool
#include <assert.h> // inline assertions
#ifdef __cplusplus
extern "C" {
@@ -119,8 +120,8 @@ mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char*
// ------------------------------------------------------
// Extended allocation functions
// ------------------------------------------------------
#define MI_SMALL_WSIZE_MAX (128)
#define MI_SMALL_SIZE_MAX (MI_SMALL_WSIZE_MAX*sizeof(void*))
#define MI_SMALL_WSIZE_MAX 128
#define MI_SMALL_SIZE_MAX (MI_SMALL_WSIZE_MAX*sizeof(size_t)) // mimalloc considers `sizeof(size_t)` as the machine word size
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
@@ -133,10 +134,17 @@ mi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize)
mi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept;
// `mi_free_size` can be more efficient (as it calls mi_free_small internally).
mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept;
// `mi_free_small` is for special applications like language runtimes.
// it should only be used to free objects from `mi_(heap_)(m|z)alloc_small` and is potentially a tiny bit faster than `mi_free`
// it should only be used to free objects from `mi_*alloc_small` and is potentially a tiny bit faster than `mi_free`
mi_decl_export void mi_free_small(void* p) mi_attr_noexcept;
// As `mi_free_small` but `p` should not be a NULL pointer.
mi_decl_export void mi_free_small_nonnull(void* p) mi_attr_noexcept;
// -------------------------------------------------------------------------------------
// Aligned allocation
// Note that `alignment` always follows `size` for consistency with unaligned
@@ -157,12 +165,13 @@ mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t new
// Typed allocation, the type is always the first parameter
// ------------------------------------------------------
#define mi_malloc_tp(tp) ((tp*)mi_malloc(sizeof(tp)))
#define mi_zalloc_tp(tp) ((tp*)mi_zalloc(sizeof(tp)))
#define mi_malloc_tp(tp) ((tp*)mi_malloc_csize(sizeof(tp)))
#define mi_zalloc_tp(tp) ((tp*)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) (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)))
@@ -187,7 +196,7 @@ typedef void (mi_cdecl mi_error_fun)(int err, void* arg);
mi_decl_export void mi_register_error(mi_error_fun* fun, void* arg);
mi_decl_export void mi_collect(bool force) mi_attr_noexcept;
mi_decl_export int mi_version(void) mi_attr_noexcept;
mi_decl_export int mi_version(void);
mi_decl_export void mi_options_print(void) mi_attr_noexcept;
mi_decl_export void mi_process_info_print(void) mi_attr_noexcept;
mi_decl_export void mi_options_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept;
@@ -329,6 +338,7 @@ mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept;
mi_decl_export void mi_arenas_print(void) mi_attr_noexcept;
mi_decl_export size_t mi_arena_min_alignment(void);
mi_decl_export size_t mi_arena_min_size(void);
mi_decl_export size_t mi_arena_max_object_size(void);
typedef void* mi_arena_id_t;
mi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size);
@@ -382,8 +392,9 @@ mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_calloc(mi_theap
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_malloc_small(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_zalloc_small(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_malloc_aligned(mi_theap_t* theap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_zalloc_aligned(mi_theap_t* theap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3);
mi_decl_nodiscard mi_decl_export void* mi_theap_realloc(mi_theap_t* theap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3);
mi_decl_nodiscard mi_decl_export void* mi_theap_rezalloc(mi_theap_t* theap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3);
// ------------------------------------------------------
// Experimental
@@ -465,25 +476,26 @@ typedef enum mi_option_e {
mi_option_deprecated_purge_extend_delay,
mi_option_disallow_arena_alloc, // 1 = do not use arena's for allocation (except if using specific arena id's)
mi_option_retry_on_oom, // retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows)
mi_option_visit_abandoned, // allow visiting theap blocks from abandoned threads (=0)
mi_option_deprecated_visit_abandoned, // allow visiting theap blocks from abandoned threads (=0)
mi_option_guarded_min, // only used when building with MI_GUARDED: minimal rounded object size for guarded objects (=0)
mi_option_guarded_max, // only used when building with MI_GUARDED: maximal rounded object size for guarded objects (=0)
mi_option_guarded_precise, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
mi_option_guarded_sample_rate, // 1 out of N allocations in the min/max range will be guarded (=1000)
mi_option_guarded_sample_seed, // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0)
mi_option_generic_collect, // collect theaps every N (=10000) generic allocation calls
mi_option_page_reclaim_on_free, // reclaim abandoned pages on a free (=0). -1 disallowr always, 0 allows if the page originated from the current theap, 1 allow always
mi_option_page_reclaim_on_free, // reclaim abandoned pages on a free (=0). -1 disallow always, 0 allows if the page originated from the current theap, 1 allow always
mi_option_page_full_retain, // retain N full (small) pages per size class (=2)
mi_option_page_max_candidates, // max candidate pages to consider for allocation (=4)
mi_option_max_vabits, // max user space virtual address bits to consider (=48)
mi_option_pagemap_commit, // commit the full pagemap (to always catch invalid pointer uses) (=0)
mi_option_page_commit_on_demand, // commit page memory on-demand
mi_option_page_max_reclaim, // don't reclaim pages of the same originating theap if we already own N pages (in that size class) (=-1 (unlimited))
mi_option_page_cross_thread_max_reclaim, // don't reclaim pages across threads if we already own N pages (in that size class) (=16)
mi_option_page_cross_thread_max_reclaim, // don't reclaim pages across threads if we already own N pages (in that size class) (=32)
mi_option_allow_thp, // allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process.
mi_option_minimal_purge_size, // set minimal purge size (in KiB) (=0). By default set to either 64 or 2048 if THP is enabled.
mi_option_arena_max_object_size, // set maximal object size that can be allocated in an arena (in KiB) (=2GiB on 64-bit).
mi_option_arena_is_numa_local, // experimental
mi_option_arena_is_numa_local, // experimental: associate local numa node with an initial arena allocation
mi_option_collect_merges_stats, // on each theap collection, the theap stats are merged automatically with the parent heap
_mi_option_last,
// legacy option names
mi_option_large_os_pages = mi_option_allow_large_os_pages,
@@ -513,13 +525,13 @@ mi_decl_export void mi_option_set_default(mi_option_t option, long value);
// note: we use `mi_cfree` as "checked free" and it checks if the pointer is in our theap before free-ing.
// -------------------------------------------------------------------------------------------------------
mi_decl_export void mi_cfree(void* p) mi_attr_noexcept;
mi_decl_export bool mi_cfree(void* p) mi_attr_noexcept;
mi_decl_export void* mi__expand(void* p, size_t newsize) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export size_t mi_malloc_good_size(size_t size) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept;
mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept;
mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size); // mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
@@ -530,19 +542,22 @@ mi_decl_nodiscard mi_decl_export int mi_reallocarr(void* ptrp, size_t count, s
mi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept;
mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept;
mi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept;
mi_decl_export void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept;
mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept;
// wide characters
#include <wchar.h> // wchar_t
mi_decl_export int mi_wdupenv_s(wchar_t** buf, size_t* size, const wchar_t* name) mi_attr_noexcept;
mi_decl_nodiscard mi_decl_export mi_decl_restrict wchar_t* mi_wcsdup(const wchar_t* s) mi_attr_noexcept mi_attr_malloc;
mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc;
// The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`.
// (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception).
// --------------------------------------------------------
// C++ wrappers
// The `mi_new` wrappers implement C++ semantics on out-of-memory
// instead of directly returning `NULL`. (and call `std::get_new_handler`
// and potentially raise a `std::bad_alloc` exception).
// --------------------------------------------------------
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new(size_t size) mi_attr_malloc mi_attr_alloc_size(1);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
@@ -554,6 +569,63 @@ mi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount,
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) mi_attr_malloc mi_attr_alloc_size(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(2, 3);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_alloc_new(mi_theap_t* theap, size_t size) mi_attr_malloc mi_attr_alloc_size(2);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_alloc_new_n(mi_theap_t* theap, size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(2, 3);
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_alloc_new_nothrow(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
// --------------------------------------------------------------------------
// Inlined constant size allocations.
// These are meant for runtime systems, or overrides where we need the best
// performance for small, constant-size allocations.
// These are only better than the regular functions if the size or alignment
// are indeed constant at the call site.
// --------------------------------------------------------------------------
// Internal machine word size allocation. `wsize` is the allocation size in machine words (`sizeof(size_t)`)
mi_decl_nodiscard mi_decl_restrict void* mi_wzalloc_small(size_t wsize) mi_attr_noexcept mi_attr_malloc;
mi_decl_nodiscard mi_decl_restrict void* mi_wmalloc_small(size_t wsize) mi_attr_noexcept mi_attr_malloc;
mi_decl_nodiscard mi_decl_restrict void* mi_theap_wmalloc_small(mi_theap_t* theap, size_t wsize) mi_attr_noexcept mi_attr_malloc;
mi_decl_nodiscard mi_decl_restrict void* mi_theap_wzalloc_small(mi_theap_t* theap, size_t wsize) mi_attr_noexcept mi_attr_malloc;
static inline size_t mi_wsize_from_size(size_t size) mi_attr_noexcept {
return (size + sizeof(size_t) - 1) / sizeof(size_t);
}
static inline mi_decl_restrict void* mi_malloc_csize(size_t size) mi_attr_noexcept {
if (size <= MI_SMALL_SIZE_MAX) { return mi_wmalloc_small(mi_wsize_from_size(size)); } else { return mi_malloc(size); }
}
static inline mi_decl_restrict void* mi_zalloc_csize(size_t size) mi_attr_noexcept {
if (size <= MI_SMALL_SIZE_MAX) { return mi_wzalloc_small(mi_wsize_from_size(size)); } else { return mi_zalloc(size); }
}
static inline mi_decl_restrict void* mi_theap_malloc_csize(mi_theap_t* theap, size_t size) mi_attr_noexcept {
assert(theap!=NULL);
if (size <= MI_SMALL_SIZE_MAX) { return mi_theap_wmalloc_small(theap,mi_wsize_from_size(size)); } else { return mi_theap_malloc(theap,size); }
}
static inline mi_decl_restrict void* mi_theap_zalloc_csize(mi_theap_t* theap, size_t size) mi_attr_noexcept {
assert(theap!=NULL);
if (size <= MI_SMALL_SIZE_MAX) { return mi_theap_wzalloc_small(theap,mi_wsize_from_size(size)); } else { return mi_theap_zalloc(theap,size); }
}
static inline void mi_free_csize(void* p, size_t size) mi_attr_noexcept {
if (size <= MI_SMALL_SIZE_MAX) { mi_free_small(p); } else { mi_free(p); }
}
static inline void mi_free_csize_nonnull(void* p, size_t size) mi_attr_noexcept {
assert(p!=NULL);
if (size <= MI_SMALL_SIZE_MAX) { mi_free_small_nonnull(p); } else { mi_free(p); }
}
static inline void mi_free_csize_aligned(void* p, size_t size, size_t aligned) mi_attr_noexcept {
if (aligned <= size && size <= MI_SMALL_SIZE_MAX) { mi_free_small(p); } else { mi_free(p); }
}
static inline void mi_free_csize_aligned_nonnull(void* p, size_t size, size_t aligned) mi_attr_noexcept {
assert(p!=NULL);
if (aligned <= size && size <= MI_SMALL_SIZE_MAX) { mi_free_small_nonnull(p); } else { mi_free(p); }
}
// ------------------------------------------------------
// C++ standard library allocator interface.
// ------------------------------------------------------
#ifdef __cplusplus
}
#endif
+23 -21
View File
@@ -9,13 +9,14 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_ATOMIC_H
// include windows.h or pthreads.h
#if defined(_WIN32)
#if defined(_WIN32) || defined(__CYGWIN__) // we use windows locks on cygwin, but otherwise treat it at unix
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#elif !defined(__wasi__) && (!defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__))
#define MI_USE_PTHREADS
#endif
#if MI_TLS_MODEL_PTHREADS || (!defined(_WIN32) && !defined(__wasi__) && (!defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)))
#define MI_USE_PTHREADS 1
#include <pthread.h>
#endif
@@ -215,24 +216,22 @@ static inline uintptr_t mi_atomic_exchange_explicit(_Atomic(uintptr_t)*p, uintpt
(void)(mo);
return (uintptr_t)MI_MSC_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange);
}
static inline void mi_atomic_thread_fence(mi_memory_order mo) {
(void)(mo);
_Atomic(uintptr_t) x = 0;
mi_atomic_exchange_explicit(&x, 1, mo);
}
static inline uintptr_t mi_atomic_load_explicit(_Atomic(uintptr_t) const* p, mi_memory_order mo) {
(void)(mo);
// assert(mo<=mi_memory_order_acquire); // others are not used by mimalloc
#if defined(_M_IX86) || defined(_M_X64)
// on x86/x64 we have a strong memory model so any load is acquire
return (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
#elif defined(_M_ARM) || defined(_M_ARM64)
if (mo == mi_memory_order_relaxed) {
return (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
}
#if !defined(__clang__) // work around __ldar missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
else if (mo <= mi_memory_order_acquire) {
return MI_MSC_XX(__ldar)((volatile const uintptr_t*)p);
}
#endif
else {
const uintptr_t u = (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
__dmb(15); // _ARM(64)_BARRIER_SY
@@ -252,9 +251,11 @@ static inline void mi_atomic_store_explicit(_Atomic(uintptr_t)*p, uintptr_t x, m
if (mo == mi_memory_order_relaxed) {
MI_MSC_XX(__iso_volatile_store)((volatile intptr_t*)p, x);
}
#if !defined(__clang__) // work around __stlr missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
else if (mo <= mi_memory_order_release) {
MI_MSC_XX(__stlr)((volatile uintptr_t*)p,x);
}
#endif
else {
mi_atomic_exchange_explicit(p, x, mo);
}
@@ -272,7 +273,7 @@ static inline int64_t mi_atomic_loadi64_explicit(_Atomic(int64_t)*p, mi_memory_o
if (mo == mi_memory_order_relaxed) {
return __iso_volatile_load64((volatile const int64_t*)p);
}
#if defined(_M_ARM64)
#if defined(_M_ARM64) && !defined(__clang__) // work around __ldar64 missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
else if (mo <= mi_memory_order_acquire) {
return __ldar64((volatile const uintptr_t*)p);
}
@@ -297,7 +298,7 @@ static inline void mi_atomic_storei64_explicit(_Atomic(int64_t)*p, int64_t x, mi
if (mo == mi_memory_order_relaxed) {
__iso_volatile_store64((volatile int64_t*)p,x);
}
#if defined(_M_ARM64)
#if defined(_M_ARM64) && !defined(__clang__) // work around __stlr64 missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
else if (mo == mi_memory_order_release) {
__stlr64((volatile uint64_t*)p, (uint64_t)x);
}
@@ -335,18 +336,18 @@ static inline void mi_atomic_void_addi64_relaxed(volatile int64_t* p, const vola
}
}
static inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)*p, int64_t x) {
static inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)* p, int64_t x) {
int64_t current;
do {
current = *p;
} while (current < x && _InterlockedCompareExchange64(p, x, current) != current);
}
static inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t*)p, int64_t i) {
static inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t)* p, int64_t i) {
mi_atomic_addi64_relaxed(p, i);
}
static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p, int64_t* exp, int64_t des) {
static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t)* p, int64_t* exp, int64_t des) {
const int64_t read = _InterlockedCompareExchange64(p, des, *exp);
if (read == *exp) {
return true;
@@ -358,17 +359,17 @@ static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p,
}
// The pointer macros cast to `uintptr_t`.
#define mi_atomic_load_ptr_acquire(tp,p) (tp*)mi_atomic_load_acquire((_Atomic(uintptr_t)*)(p))
#define mi_atomic_load_ptr_relaxed(tp,p) (tp*)mi_atomic_load_relaxed((_Atomic(uintptr_t)*)(p))
#define mi_atomic_load_ptr_acquire(tp,p) ((tp*)mi_atomic_load_acquire((_Atomic(uintptr_t)*)(p)))
#define mi_atomic_load_ptr_relaxed(tp,p) ((tp*)mi_atomic_load_relaxed((_Atomic(uintptr_t)*)(p)))
#define mi_atomic_store_ptr_release(tp,p,x) mi_atomic_store_release((_Atomic(uintptr_t)*)(p),(uintptr_t)(x))
#define mi_atomic_store_ptr_relaxed(tp,p,x) mi_atomic_store_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)(x))
#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des) mi_atomic_cas_strong_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
#define mi_atomic_exchange_ptr_relaxed(tp,p,x) (tp*)mi_atomic_exchange_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
#define mi_atomic_exchange_ptr_release(tp,p,x) (tp*)mi_atomic_exchange_release((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) (tp*)mi_atomic_exchange_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
#define mi_atomic_exchange_ptr_relaxed(tp,p,x) ((tp*)mi_atomic_exchange_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)x))
#define mi_atomic_exchange_ptr_release(tp,p,x) ((tp*)mi_atomic_exchange_release((_Atomic(uintptr_t)*)(p),(uintptr_t)x))
#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) ((tp*)mi_atomic_exchange_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t)x))
#define mi_atomic_loadi64_acquire(p) mi_atomic(loadi64_explicit)(p,mi_memory_order(acquire))
#define mi_atomic_loadi64_relaxed(p) mi_atomic(loadi64_explicit)(p,mi_memory_order(relaxed))
@@ -417,7 +418,7 @@ typedef _Atomic(uintptr_t) mi_atomic_guard_t;
#define mi_lock_maybe(lock,acquire) for(bool _mi_go = (acquire ? (mi_lock_acquire(lock),true) : true); _mi_go; _mi_go = (acquire ? (mi_lock_release(lock),false) : false) )
#if defined(_WIN32)
#if defined(_WIN32) || defined(__CYGWIN__)
typedef struct mi_lock_s {
SRWLOCK mutex; // slim reader-writer lock
@@ -496,10 +497,10 @@ static inline void mi_lock_release(mi_lock_t* lock) {
lock->mutex.unlock();
}
static inline void mi_lock_init(mi_lock_t* lock) {
new(&lock->mutex) std::mutex();
new(&lock->mutex) std::mutex(); // in-place constructor
}
static inline void mi_lock_done(mi_lock_t* lock) {
(void)(lock);
lock->mutex.~mutex(); // in-place destructor
}
#else
@@ -524,6 +525,7 @@ static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
return mi_atomic_cas_strong_acq_rel(&lock->mutex, &expected, (uintptr_t)1);
}
static inline void mi_lock_acquire(mi_lock_t* lock) {
size_t ticks = 0;
for (int i = 0; i < 10000; i++) { // for at most 10000 tries?
if (mi_lock_try_acquire(lock)) return;
_mi_prim_thread_yield();
+28 -6
View File
@@ -16,6 +16,15 @@ terms of the MIT license. A copy of the license can be found in the file
#include <stddef.h> // size_t
#include <stdint.h> // int64_t etc
#include <stdbool.h> // bool
#include <limits.h> // LONG_MAX
#if defined(__cplusplus)
#define mi_decl_externc extern "C"
#define mi_init_struct_zero { }
#else
#define mi_decl_externc
#define mi_init_struct_zero { 0 }
#endif
// ------------------------------------------------------
// Size of a pointer.
@@ -102,13 +111,13 @@ typedef int32_t mi_ssize_t;
#include <intrin.h>
#endif
#if MI_ARCH_X64 && defined(__AVX2__) && !defined(__BMI2__) // msvc
#if MI_ARCH_X64 && defined(__AVX2__) && !defined(__BMI2__) // avx2 implies bmi2
#define __BMI2__ 1
#endif
#if MI_ARCH_X64 && (defined(__AVX2__) || defined(__BMI2__)) && !defined(__BMI1__) // msvc
#if MI_ARCH_X64 && (defined(__AVX2__) || defined(__BMI2__) || defined(__BMI__)) && !defined(__BMI1__) // bmi2 implies bmi1
#define __BMI1__ 1
#endif
#if MI_ARCH_X64 && defined(__AVX2__) && !defined(__LZCNT__) // msvc
#if MI_ARCH_X64 && defined(__AVX2__) && !defined(__LZCNT__) // avx2 implies lzcnt
#define __LZCNT__ 1
#endif
@@ -126,9 +135,18 @@ typedef int32_t mi_ssize_t;
#define MI_MAX_VABITS (32)
#endif
// the MI_MIN_VABITS determine how many bits of the address space are always committed in the page_map
#if MI_MAX_VABITS <= 32
#define MI_MIN_VABITS (32)
#elif MI_MAX_VABITS <= 42
#define MI_MIN_VABITS MI_MAX_VABITS
#else
#define MI_MIN_VABITS (43) /* 8 TiB */
#endif
// use a flat page-map or a 2-level one
#ifndef MI_PAGE_MAP_FLAT
#if MI_MAX_VABITS <= 40 && !defined(__APPLE__) && MI_SECURE==0 && !MI_PAGE_META_IS_SEPARATED
#if MI_MAX_VABITS <= 40 && !defined(__APPLE__) && !MI_SECURE && !MI_FREE_IS_CHECKED && MI_FREE_USE_PAGEMAP
#define MI_PAGE_MAP_FLAT 1
#else
#define MI_PAGE_MAP_FLAT 0
@@ -177,12 +195,16 @@ typedef int32_t mi_ssize_t;
-------------------------------------------------------------------------------- */
size_t _mi_popcount_generic(size_t x);
extern bool _mi_cpu_has_popcnt;
static inline size_t mi_popcount(size_t x) {
#if mi_has_builtinz(popcount)
return mi_builtinz(popcount)(x);
#elif defined(_MSC_VER) && (MI_ARCH_X64 || MI_ARCH_X86 || MI_ARCH_ARM64 || MI_ARCH_ARM32)
return mi_msc_builtinz(__popcnt)(x);
#elif defined(_MSC_VER) && (MI_ARCH_ARM64 || MI_ARCH_ARM32)
return mi_msc_builtinz(_CountOneBits)(x);
#elif defined(_MSC_VER) && (MI_ARCH_X64 || MI_ARCH_X86)
if (_mi_cpu_has_popcnt) { return mi_msc_builtinz(__popcnt)(x); }
else { return _mi_popcount_generic(x); } // see issue #1291
#elif MI_ARCH_X64 && defined(__BMI1__)
return (size_t)_mm_popcnt_u64(x);
#else
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,434 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#pragma once
#ifndef MIMALLOC_PRIM_TLS_H
#define MIMALLOC_PRIM_TLS_H
#include "types.h"
#include "internal.h" // mi_decl_hidden
// --------------------------------------------------------------------------
// We need fast access to both a unique thread id (in `free.c:mi_free`) and
// to a thread-local theap pointer (in `alloc.c:mi_malloc`).
//
// For performance, we tend to use specialized code for various platforms.
// This leads to quite a few ifdefs but it is just for performance and there
// is always a portable fallback (based on regular thread local variables).
//
// Windows : use NtCurrentTeB and TlsAlloc (MI_TLS_MODEL_WIN32)
// Linux,FreeBSD : use thread locals with the initial-exec model (MI_TLS_MODEL_LOCAL)
// macOS : use pthread locals with assembly for the thread-id (MI_TLS_MODEL_PTHREADS)
// Android,OpenBSD : use pthread locals (MI_TLS_MODEL_PTHREADS). todo: maybe on Android MI_TLS_MODEL_LOCAL is better?
// --------------------------------------------------------------------------
// static inline void** mi_prim_thread_pointer(void); // get a pointer to the thread local storage
// static inline void* mi_prim_tls_slot(size_t slot); // directly read an entry from the thread local storage block
// static inline void mi_prim_tls_slot_set(size_t slot, void* value);
static inline mi_threadid_t _mi_prim_thread_id(void); // get a unique id for a thread
static inline mi_theap_t* _mi_theap_default(void); // the default thread local theap
static inline mi_theap_t* _mi_theap_cached(void); // last used thread local theap using the _heap_ api
static inline bool _mi_thread_is_initialized(void); // a thread is initialized if it has a default theap
static inline mi_theap_t* _mi_heap_theap(mi_heap_t* heap); // get the thread local theap belonging to a heap
static inline mi_theap_t* _mi_heap_theap_peek(const mi_heap_t* heap); // get the theap but don't update _mi_theap_cached
static inline mi_theap_t* _mi_page_associated_theap_peek(const mi_page_t* page); // get the theap associated with a page (used in `mi_free_collect_mt`)
// Default TLS model
#if !defined(MI_TLS_MODEL_LOCAL) && !defined(MI_TLS_MODEL_PTHREADS) && !defined(MI_TLS_MODEL_FIXED) && !defined(MI_TLS_MODEL_WIN32)
#if defined(_WIN32)
#define MI_TLS_MODEL_WIN32 1
#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__ANDROID__) // and FreeBSD?
#define MI_TLS_MODEL_PTHREADS 1
#else
#define MI_TLS_MODEL_LOCAL 1
#endif
#endif
//-------------------------------------------------------------------
// Access to the thread pointer
//-------------------------------------------------------------------
// Do we have __builtin_thread_pointer? This would be the preferred way to get a unique thread id
// but unfortunately, it seems we cannot test for this reliably at this time (see issue #883)
// Nevertheless, it seems needed on older graviton platforms (see issue #851).
// For now, we only enable this for specific platforms.
#if !defined(MI_USE_BUILTIN_THREAD_POINTER) /* allow user override */
#if !defined(__APPLE__) /* on apple (M1) the wrong register is read (tpidr_el0 instead of tpidrro_el0) so fall back to TLS slot assembly (<https://github.com/microsoft/mimalloc/issues/343#issuecomment-763272369>)*/ \
&& !defined(__CYGWIN__) \
&& !defined(MI_LIBC_MUSL) \
&& (!defined(__clang_major__) || __clang_major__ >= 14) /* older clang versions emit bad code; fall back to using the TLS slot (<https://lore.kernel.org/linux-arm-kernel/202110280952.352F66D8@keescook/T/>) */
#if (defined(__GNUC__) && (__GNUC__ >= 7) && defined(__aarch64__)) /* aarch64 for older gcc versions (issue #851) */ \
|| (defined(__GNUC__) && (__GNUC__ >= 7) && defined(__riscv)) \
|| (defined(__GNUC__) && (__GNUC__ >= 11) && defined(__x86_64__)) \
|| (defined(__clang_major__) && (__clang_major__ >= 14) && (defined(__aarch64__) || defined(__x86_64__) || defined(__riscv)))
#define MI_USE_BUILTIN_THREAD_POINTER 1
#endif
#endif
#endif
#if defined(MI_PRIM_THREAD_POINTER) // potential user override
static inline void** mi_prim_thread_pointer(void) {
return MI_PRIM_THREAD_POINTER();
}
#elif defined(_WIN32)
static inline void** mi_prim_thread_pointer(void) {
return (void**)NtCurrentTeb();
}
#elif MI_USE_BUILTIN_THREAD_POINTER
static inline void** mi_prim_thread_pointer(void) {
return (void**)__builtin_thread_pointer();
}
#elif defined(__GNUC__) && !defined(__CYGWIN__)
#if defined(__aarch64__)
static inline void** mi_prim_thread_pointer(void) {
void** tcb;
#if defined(__APPLE__) // M1, issue rgb(62, 76, 62)
__asm__ ("mrs %0, tpidrro_el0\n\tbic %0, %0, #7" : "=r" (tcb));
#else
__asm__ ("mrs %0, tpidr_el0" : "=r" (tcb));
#endif
return tcb;
}
#elif defined(__riscv)
static inline void** mi_prim_thread_pointer(void) {
void** tcb;
__asm__ ("mv %0, tp" : "=r" (tcb));
return tcb;
}
#elif defined(__arm__)
static inline void** mi_prim_thread_pointer(void) {
void** tcb;
__asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\n\tbic %0, %0, #3" : "=r" (tcb));
return tcb;
}
#elif defined(__i386__)
static inline void** mi_prim_thread_pointer(void) {
void** tcb;
__asm__ ("movl %%gs:0, %0" : "=r" (tcb) : : ); // x86 32-bit always uses GS
return tcb;
}
#elif defined(__x86_64__)
static inline void** mi_prim_thread_pointer(void) {
void** tcb;
#if defined(__APPLE__)
__asm__ ("movq %%gs:0, %0" : "=r" (tcb) : : ); // x86_64 macOSX uses GS
#elif (MI_SIZE_SIZE==4)
__asm__ ("movl %%fs:0, %0" : "=r" (tcb) : : ); // x32 ABI
#else
__asm__ ("movq %%fs:0, %0" : "=r" (tcb) : : ); // x86_64 Linux, BSD uses FS
#endif
return tcb;
}
#else
#define MI_NO_THREAD_POINTER (1)
#endif
#elif MI_USE_PTHREADS && defined(__APPLE__)
static inline void** mi_prim_thread_pointer(void) {
return (void**)pthread_self();
}
#else
#define MI_NO_THREAD_POINTER (1)
#endif
#if !MI_NO_THREAD_POINTER
#define MI_HAS_TLS_SLOT (1)
static inline void* mi_prim_tls_slot(size_t slot) {
#if defined(_WIN32)
#if (_M_X64 || _M_AMD64) && !defined(_M_ARM64EC)
return (void*)__readgsqword((unsigned long)(slot*sizeof(void*))); // direct load at offset from gs
#elif _M_IX86 && !defined(_M_ARM64EC)
return (void*)__readfsdword((unsigned long)(slot*sizeof(void*))); // direct load at offset from fs
#else
return mi_prim_thread_pointer()[slot];
#endif
#else
return mi_prim_thread_pointer()[slot];
#endif
}
static inline void mi_prim_tls_slot_set(size_t slot, void* value) {
mi_prim_thread_pointer()[slot] = value;
}
#endif
/* ----------------------------------------------------------------------------------------
Get a unique thread-id for a thread
---------------------------------------------------------------------------------------- */
// Get a unique id for the current thread.
#if defined(MI_PRIM_THREAD_ID)
static inline mi_threadid_t __mi_prim_thread_id(void) {
return MI_PRIM_THREAD_ID(); // used for example by CPython for a free threaded build (see python/cpython#115488)
}
#elif !MI_NO_THREAD_POINTER
static inline mi_threadid_t __mi_prim_thread_id(void) {
#if defined(__BIONIC__)
return (mi_threadid_t)mi_prim_tls_slot(1);
#else
return (mi_threadid_t)mi_prim_thread_pointer();
#endif
}
#else
// otherwise use portable C, taking the address of a thread local variable (this is still very fast on most platforms).
extern mi_decl_hidden mi_decl_thread void* __mi_thread_id_helper;
static inline mi_threadid_t __mi_prim_thread_id(void) {
return (uintptr_t)&__mi_thread_id_helper;
}
#endif
static inline mi_threadid_t _mi_prim_thread_id(void) {
const mi_threadid_t tid = __mi_prim_thread_id();
mi_assert_internal(tid > MI_THREADID_DETACHED);
mi_assert_internal((tid & MI_PAGE_FLAG_MASK) == 0); // bottom 2 bits are clear?
return tid;
}
/* ----------------------------------------------------------------------------------------
Get the thread local default theap: `_mi_theap_default()` (and the cached heap `_mi_theap_cached`).
This is inlined here as it is on the fast path for allocation functions.
We have 4 models:
- MI_TLS_MODEL_LOCAL: use regular thread local (default on Linux, FreeBSD, etc)
On most platforms (Linux, FreeBSD, NetBSD, etc), this just returns a
thread local variable (`__mi_theap_default`). With the initial-exec TLS model this ensures
that the storage will always be available and properly initialized (with an empty theap).
On some platforms the underlying TLS implementation (or the loader) will call itself `malloc`
on a first access to a thread local and recurse in the MI_TLS_MODEL_LOCAL.
A way around this is to define MI_TLS_RECURSE_GUARD which adds an extra check if the process
is initialized before accessing the thread-local. This is a check in the fast path though
so this should be avoided.
- MI_TLS_MODEL_PTHREADS: use `pthread_getspecific`. (default on macOS and OpenBSD, maybe good for Android as well?)
Use pthread local storage. Can be as fast as thread locals on many platforms (like recent macOS).
- MI_TLS_MODEL_FIXED: use a fixed slot in the TLS block.
This reserves an unused and fixed TLS slot. This is fast and avoids the problem
where the underlying TLS implementation (or the loader) will call itself `malloc`
on a first access to a thread local (and recurse in the MI_TLS_MODEL_LOCAL).
This goes wrong though if the OS or a library uses the same fixed slot, and also
prevents multiple instances of mimalloc in the same process.
- MI_TLS_MODEL_WIN32: use a dynamically allocated slot with TlsAlloc. (default on Windows)
We use TlsAlloc'd slot. First tries to use one of the "direct" first 64 slots which
are the fastest, but falls back to using "expansion" slots when needed (up to 1088 slots).
(If the allocated slot happens to always be under 64 for a particular program,
one might use cmake with `-DMI_WIN_DIRECT_TLS=ON` to skip the expansion slot test in the fast path.)
Each model should define `MI_THEAP_INITASNULL` to signify that the initial value
returned from `_mi_theap_default()` can be `NULL` (instead of the address of the empty heap).
This incurs an extra check in the fast path (but can often be combined in an existing check).
------------------------------------------------------------------------------------------- */
#if !defined(MI_TLS_RECURSE_GUARD) && MI_TLS_MODEL_LOCAL && defined(__APPLE__)
#define MI_TLS_RECURSE_GUARD 1 // macOS can allocate on thread-local initialization
#endif
// Declared this way to optimize register spills and branches
mi_decl_cold mi_decl_noinline mi_theap_t* _mi_theap_empty_get(void);
static inline mi_theap_t* __mi_theap_empty(void) {
#if __GNUC__
__asm(""); // prevent conditional load
return (mi_theap_t*)&_mi_theap_empty;
#else
return _mi_theap_empty_get();
#endif
}
#if MI_TLS_MODEL_LOCAL
// Thread local with an initial value (default on Linux). Very efficient.
extern mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_default; // default theap to allocate from
extern mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_cached; // theap from the last used heap
// defined in `init.c`; do not use these directly
extern mi_decl_hidden bool _mi_process_is_initialized; // has mi_process_init been called?
static inline mi_theap_t* _mi_theap_default(void) {
#if defined(MI_TLS_RECURSE_GUARD)
if mi_unlikely(!_mi_process_is_initialized) return _mi_theap_empty_get();
#endif
return __mi_theap_default;
}
static inline mi_theap_t* _mi_theap_cached(void) {
return __mi_theap_cached;
}
#elif MI_TLS_MODEL_PTHREADS
// Dynamic pthread slots. This can be fast depending on the platform (default for macOS and OpenBSD)
// On some platforms (like macOS), the loader might allocate on thread local declarations which
// can be avoided with pthreads.
#define MI_THEAP_INITASNULL 1
extern mi_decl_hidden _Atomic(pthread_key_t) _mi_theap_default_key;
extern mi_decl_hidden _Atomic(pthread_key_t) _mi_theap_cached_key;
static inline mi_theap_t* _mi_theap_default(void) {
pthread_key_t key = mi_atomic_load_relaxed(&_mi_theap_default_key);
#if defined(__APPLE__) && defined(__aarch64__) && MI_HAS_TLS_SLOT
// on apple arm64, the pthread specific slots are direct slots; inline it to avoid a stack frame setup in `mi_malloc`
// todo: this is probably also the case on x64 and power pc?
if (key == MI_PTHREAD_KEY_INVALID) return NULL;
return (mi_theap_t*)mi_prim_tls_slot(key);
#else
return (mi_theap_t*)mi_pthread_key_get(key);
#endif
}
static inline mi_theap_t* _mi_theap_cached(void) {
pthread_key_t key = mi_atomic_load_relaxed(&_mi_theap_cached_key);
#if defined(__APPLE__) && defined(__aarch64__) && MI_HAS_TLS_SLOT
if (key == MI_PTHREAD_KEY_INVALID) return NULL;
return (mi_theap_t*)mi_prim_tls_slot(key);
#else
return (mi_theap_t*)mi_pthread_key_get(key);
#endif
}
#elif MI_TLS_MODEL_WIN32
// Dynamic TLS slots -- this is the default on Windows.
#define MI_THEAP_INITASNULL 1
// We try to use direct slots (64 available), but can also use the expansion slots (upto 1024 extra available)
// See <https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm> for the offsets.
#if MI_SIZE_SIZE==4
#define MI_TLS_EXPANSION_SLOT (0x0F94 / MI_SIZE_SIZE)
#else
#define MI_TLS_EXPANSION_SLOT (0x1780 / MI_SIZE_SIZE)
#endif
extern mi_decl_hidden _Atomic(size_t) _mi_theap_default_slot;
extern mi_decl_hidden _Atomic(size_t) _mi_theap_cached_slot;
extern mi_decl_hidden _Atomic(size_t) _mi_theap_default_expansion_slot;
extern mi_decl_hidden _Atomic(size_t) _mi_theap_cached_expansion_slot;
static inline mi_theap_t* _mi_theap_default(void) {
const size_t slot = mi_atomic_load_relaxed(&_mi_theap_default_slot);
mi_theap_t* theap = (mi_theap_t*)mi_prim_tls_slot(slot);
#if !MI_WIN_DIRECT_TLS
if mi_unlikely(slot==MI_TLS_EXPANSION_SLOT) { // in TlsExpansionSlots ?
mi_theap_t** const eslots = (mi_theap_t**)theap; // theap is actually the expansion slot entry
if mi_likely(eslots!=NULL) { // is it initialized? (on this thread)
theap = eslots[mi_atomic_load_relaxed(&_mi_theap_default_expansion_slot)];
}
}
#endif
return theap;
}
static inline mi_theap_t* _mi_theap_cached(void) {
const size_t slot = mi_atomic_load_relaxed(&_mi_theap_cached_slot);
mi_theap_t* theap = (mi_theap_t*)mi_prim_tls_slot(slot);
#if !MI_WIN_DIRECT_TLS
if mi_unlikely(slot==MI_TLS_EXPANSION_SLOT) { // in TlsExpansionSlots ?
mi_theap_t** const eslots = (mi_theap_t**)theap; // theap is the expansion slot entry
if mi_likely(eslots!=NULL) { // is it initialized? (on this thread)
theap = eslots[mi_atomic_load_relaxed(&_mi_theap_cached_expansion_slot)];
}
}
#endif
return theap;
}
#elif MI_TLS_MODEL_FIXED
// Fixed TLS slot. Can be the fastest approach, but does not work if there are multiple instances of
// mimalloc in the same process. Most OS's do not have official user reserved fixed slots so this cannot be
// guaranteed to work in general.
#define MI_THEAP_INITASNULL 1
#if !MI_HAS_TLS_SLOT
#error this platform cannot support MI_TLS_MODEL_FIXED without defining mi_prim_tls_slot
#endif
#if !defined(MI_TLS_MODEL_FIXED_DEFAULT)
#if defined(__APPLE__) && !defined(__POWERPC__) // macOS on arm64 or x64
// we use the last two swift framework slots which seem unused.
// we may want to use slot 6 and 11 instead which are only used by Windows emulation.
// see <https://github.com/apple/darwin-libpthread/blob/main/private/pthread/tsd_private.h#L99> for assigned slots
#define MI_TLS_MODEL_FIXED_DEFAULT 108
#define MI_TLS_MODEL_FIXED_CACHED 109
#elif defined(_WIN32)
// we use two seemingly unused fields in the Windows TEB.
// see <https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm>
#define MI_TLS_MODEL_FIXED_DEFAULT 5 // arbitrary user pointer
#define MI_TLS_MODEL_FIXED_CACHED 7 // environment pointer (used by OS2)
#else
#error define the TLS model fixed slots (or change the TLS model away from MI_TLS_MODEL_FIXED)
#endif
#endif
static inline mi_theap_t* _mi_theap_default(void) {
return (mi_theap_t*)mi_prim_tls_slot(MI_TLS_MODEL_FIXED_DEFAULT);
}
static inline mi_theap_t* _mi_theap_cached(void) {
return (mi_theap_t*)mi_prim_tls_slot(MI_TLS_MODEL_FIXED_CACHED);
}
#else
#error "no TLS model is defined for this platform?"
#endif
// Check if a thread is initialized (without using a thread-local if using fixed slots)
static inline bool _mi_thread_is_initialized(void) {
return mi_theap_is_initialized(_mi_theap_default());
}
// Get (and possible create) the theap belonging to a heap
// We cache the last accessed theap in `_mi_theap_cached` for better performance.
static inline mi_theap_t* _mi_heap_theap(mi_heap_t* heap) {
mi_theap_t* theap = _mi_theap_cached();
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL && _mi_theap_heap_peek(theap)==heap) return theap;
#else
if mi_likely(_mi_theap_heap_peek(theap)==heap) return theap;
#endif
return _mi_heap_theap_get_or_init(heap);
}
static inline mi_theap_t* _mi_heap_theap_cached(mi_heap_t* heap) {
mi_theap_t* theap = _mi_theap_cached();
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL && _mi_theap_heap_peek(theap)==heap) return theap;
#else
if mi_likely(_mi_theap_heap_peek(theap)==heap) return theap;
#endif
return NULL;
}
// Get the theap belonging to a heap without creating it if it is not yet initialized.
static inline mi_theap_t* _mi_heap_theap_peek(const mi_heap_t* heap) {
mi_theap_t* theap = _mi_theap_cached();
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL && _mi_theap_heap_peek(theap)==heap) return theap;
#else
if mi_likely(_mi_theap_heap_peek(theap)==heap) return theap;
#endif
theap = (mi_theap_t*)_mi_thread_local_get(heap->theap); // don't update the cache on a query
mi_assert_internal(theap==NULL || (!_mi_is_empty_theap(theap) && theap->heap==heap));
return theap;
}
// Find the associated theap or NULL if it does not exist (during shutdown)
// Should be fast as it is called in `free.c:mi_free_try_collect`.
static inline mi_theap_t* _mi_page_associated_theap_peek(const mi_page_t* page) {
mi_heap_t* const heap = mi_page_heap(page);
mi_theap_t* const theap = (mi_theap_t*)_mi_thread_local_get(heap->theap);
if (theap==NULL || theap->tld==NULL /* heap destroy */) return NULL;
if (theap->heap != heap) return NULL; // should never happen, but can happen for a free across subprocesses, which can happen during pthread tls storage deallocation
mi_assert_internal(!_mi_is_empty_theap(theap) && mi_theap_matches_thread(theap));
// note: for pages allocated by a detached theap, the returned theap may not be detached
return theap;
}
#endif // MI_PRIM_TLS_H
+8 -414
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -7,7 +7,8 @@ terms of the MIT license. A copy of the license can be found in the file
#pragma once
#ifndef MIMALLOC_PRIM_H
#define MIMALLOC_PRIM_H
#include "internal.h" // mi_decl_hidden
#include "types.h"
// --------------------------------------------------------------------------
// This file specifies the primitive portability API.
@@ -23,7 +24,7 @@ terms of the MIT license. A copy of the license can be found in the file
// OS memory configuration
typedef struct mi_os_mem_config_s {
size_t page_size; // default to 4KiB
size_t large_page_size; // 0 if not supported, usually 2MiB (4MiB on Windows)
size_t large_page_size; // 0 if not supported, usually 2MiB
size_t alloc_granularity; // smallest allocation size (usually 4KiB, on Windows 64KiB)
size_t physical_memory_in_kib; // physical memory size in KiB
size_t virtual_address_bits; // usually 48 or 56 bits on 64-bit systems. (used to determine secure randomization)
@@ -107,7 +108,9 @@ void _mi_prim_out_stderr( const char* msg );
// Get an environment variable. (only for options)
// name != NULL, result != NULL, result_size >= 64
bool _mi_prim_getenv(const char* name, char* result, size_t result_size);
// Return 1 for success, 0 if not found,
// and -1 on error (for example, if `getenv` cannot be called yet during preloading).
int _mi_prim_getenv(const char* name, char* result, size_t result_size);
// Fill a buffer with strong randomness; return `false` on error or if
@@ -126,417 +129,8 @@ void _mi_prim_thread_associate_default_theap(mi_theap_t* theap);
// Is this thread part of a thread pool?
bool _mi_prim_thread_is_in_threadpool(void);
// Yield to other threads. Should be similar to `sleep(0)`.
// Yield to other threads. Should be similar to `sleep(0)`.
// Is called only in rare situations and does not have to be lightning fast.
void _mi_prim_thread_yield(void);
//-------------------------------------------------------------------
// Access to TLS (thread local storage) slots.
// We need fast access to both a unique thread id (in `free.c:mi_free`) and
// to a thread-local theap pointer (in `alloc.c:mi_malloc`).
// To achieve this we use specialized code for various platforms.
//-------------------------------------------------------------------
// On some libc + platform combinations we can directly access a thread-local storage (TLS) slot.
// The TLS layout depends on both the OS and libc implementation so we use specific tests for each main platform.
// If you test on another platform and it works please send a PR :-)
// see also https://akkadia.org/drepper/tls.pdf for more info on the TLS register.
//
// Note: we would like to prefer `__builtin_thread_pointer()` nowadays instead of using assembly,
// but unfortunately we can not detect support reliably (see issue #883)
// We also use it on Apple OS as we use a TLS slot for the default theap there.
#if (defined(_WIN32)) || \
(defined(__GNUC__) && ( \
(defined(__GLIBC__) && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \
|| (defined(__APPLE__) && (defined(__x86_64__) || defined(__aarch64__) || defined(__POWERPC__))) \
|| (defined(__BIONIC__) && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \
|| (defined(__FreeBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \
|| (defined(__OpenBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \
))
static inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {
void* res;
const size_t ofs = (slot*sizeof(void*));
#if defined(_WIN32)
#if (_M_X64 || _M_AMD64) && !defined(_M_ARM64EC)
res = (void*)__readgsqword((unsigned long)ofs); // direct load at offset from gs
#elif _M_IX86 && !defined(_M_ARM64EC)
res = (void*)__readfsdword((unsigned long)ofs); // direct load at offset from fs
#else
res = ((void**)NtCurrentTeb())[slot]; MI_UNUSED(ofs);
#endif
#elif defined(__i386__)
__asm__("movl %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86 32-bit always uses GS
#elif defined(__APPLE__) && defined(__x86_64__)
__asm__("movq %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 macOSX uses GS
#elif defined(__x86_64__) && (MI_INTPTR_SIZE==4)
__asm__("movl %%fs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x32 ABI
#elif defined(__x86_64__)
__asm__("movq %%fs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 Linux, BSD uses FS
#elif defined(__arm__)
void** tcb; MI_UNUSED(ofs);
__asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb));
res = tcb[slot];
#elif defined(__aarch64__)
void** tcb; MI_UNUSED(ofs);
#if defined(__APPLE__) // M1, issue #343
__asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb));
#else
__asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb));
#endif
res = tcb[slot];
#elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781
MI_UNUSED(ofs);
res = pthread_getspecific(slot);
#else
#define MI_HAS_TLS_SLOT 0
MI_UNUSED(ofs);
res = NULL;
#endif
return res;
}
#ifndef MI_HAS_TLS_SLOT
#define MI_HAS_TLS_SLOT 1
#endif
// setting a tls slot is only used on macOS for now
static inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexcept {
const size_t ofs = (slot*sizeof(void*));
#if defined(_WIN32)
((void**)NtCurrentTeb())[slot] = value; MI_UNUSED(ofs);
#elif defined(__i386__)
__asm__("movl %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // 32-bit always uses GS
#elif defined(__APPLE__) && defined(__x86_64__)
__asm__("movq %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 macOS uses GS
#elif defined(__x86_64__) && (MI_INTPTR_SIZE==4)
__asm__("movl %1,%%fs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x32 ABI
#elif defined(__x86_64__)
__asm__("movq %1,%%fs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 Linux, BSD uses FS
#elif defined(__arm__)
void** tcb; MI_UNUSED(ofs);
__asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb));
tcb[slot] = value;
#elif defined(__aarch64__)
void** tcb; MI_UNUSED(ofs);
#if defined(__APPLE__) // M1, issue #343
__asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb));
#else
__asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb));
#endif
tcb[slot] = value;
#elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781
MI_UNUSED(ofs);
pthread_setspecific(slot, value);
#else
MI_UNUSED(ofs); MI_UNUSED(value);
#endif
}
#endif
// defined in `init.c`; do not use these directly
extern mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_main; // theap belonging to the main heap
extern mi_decl_hidden bool _mi_process_is_initialized; // has mi_process_init been called?
//-------------------------------------------------------------------
// Get a fast unique thread id.
//
// Getting the thread id should be performant as it is called in the
// fast path of `_mi_free` and we specialize for various platforms as
// inlined definitions. Regular code should call `init.c:_mi_thread_id()`.
// We only require _mi_prim_thread_id() to return a unique id
// for each thread (unequal to zero).
//-------------------------------------------------------------------
// Do we have __builtin_thread_pointer? This would be the preferred way to get a unique thread id
// but unfortunately, it seems we cannot test for this reliably at this time (see issue #883)
// Nevertheless, it seems needed on older graviton platforms (see issue #851).
// For now, we only enable this for specific platforms.
#if !defined(MI_USE_BUILTIN_THREAD_POINTER) /* allow user override */
#if !defined(__APPLE__) /* on apple (M1) the wrong register is read (tpidr_el0 instead of tpidrro_el0) so fall back to TLS slot assembly (<https://github.com/microsoft/mimalloc/issues/343#issuecomment-763272369>)*/ \
&& !defined(__CYGWIN__) \
&& !defined(MI_LIBC_MUSL) \
&& (!defined(__clang_major__) || __clang_major__ >= 14) /* older clang versions emit bad code; fall back to using the TLS slot (<https://lore.kernel.org/linux-arm-kernel/202110280952.352F66D8@keescook/T/>) */
#if (defined(__GNUC__) && (__GNUC__ >= 7) && defined(__aarch64__)) /* aarch64 for older gcc versions (issue #851) */ \
|| (defined(__GNUC__) && (__GNUC__ >= 11) && defined(__x86_64__)) \
|| (defined(__clang_major__) && (__clang_major__ >= 14) && (defined(__aarch64__) || defined(__x86_64__)))
#define MI_USE_BUILTIN_THREAD_POINTER 1
#endif
#endif
#endif
static inline mi_threadid_t __mi_prim_thread_id(void) mi_attr_noexcept;
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
const mi_threadid_t tid = __mi_prim_thread_id();
mi_assert_internal(tid > 1);
mi_assert_internal((tid & MI_PAGE_FLAG_MASK) == 0); // bottom 2 bits are clear?
return tid;
}
// Get a unique id for the current thread.
#if defined(MI_PRIM_THREAD_ID)
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
const mi_threadid_t tid = MI_PRIM_THREAD_ID(); // used for example by CPython for a free threaded build (see python/cpython#115488)
mi_assert_internal( (tid & 0x03) == 0 ); // mimalloc reserves the bottom 2 bits
return tid;
}
#elif defined(_WIN32)
static inline mi_threadid_t __mi_prim_thread_id(void) mi_attr_noexcept {
// Windows: works on Intel and ARM in both 32- and 64-bit
return (uintptr_t)NtCurrentTeb();
}
#elif MI_USE_BUILTIN_THREAD_POINTER
static inline mi_threadid_t __mi_prim_thread_id(void) mi_attr_noexcept {
// Works on most Unix based platforms with recent compilers
return (uintptr_t)__builtin_thread_pointer();
}
#elif MI_HAS_TLS_SLOT
static inline mi_threadid_t __mi_prim_thread_id(void) mi_attr_noexcept {
#if defined(__BIONIC__)
// issue #384, #495: on the Bionic libc (Android), slot 1 is the thread id
// see: https://github.com/aosp-mirror/platform_bionic/blob/c44b1d0676ded732df4b3b21c5f798eacae93228/libc/platform/bionic/tls_defines.h#L86
return (uintptr_t)mi_prim_tls_slot(1);
#else
// in all our other targets, slot 0 is the thread id
// glibc: https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/x86_64/nptl/tls.h
// apple: https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L36
return (uintptr_t)mi_prim_tls_slot(0);
#endif
}
#else
// otherwise use portable C, taking the address of a thread local variable (this is still very fast on most platforms).
static inline mi_threadid_t __mi_prim_thread_id(void) mi_attr_noexcept {
return (uintptr_t)&__mi_theap_main;
}
#endif
/* ----------------------------------------------------------------------------------------
Get the thread local default theap: `_mi_theap_default()` (and the cached heap `_mi_theap_cached`).
This is inlined here as it is on the fast path for allocation functions.
We have 4 models:
- MI_TLS_MODEL_THREAD_LOCAL: use regular thread local (default on Linux, FreeBSD, etc)
On most platforms (Linux, FreeBSD, NetBSD, etc), this just returns a
thread local variable (`__mi_theap_default`). With the initial-exec TLS model this ensures
that the storage will always be available and properly initialized (with an empty theap).
On some platforms the underlying TLS implementation (or the loader) will call itself `malloc`
on a first access to a thread local and recurse in the MI_TLS_MODEL_THREAD_LOCAL.
A way around this is to define MI_TLS_RECURSE_GUARD which adds an extra check if the process
is initialized before accessing the thread-local. This is a check in the fast path though
so this should be avoided.
- MI_TLS_MODEL_FIXED_SLOT: use a fixed slot in the TLS block (default on macOS)
This reserves an unused and fixed TLS slot. This is fast and avoids the problem
where the underlying TLS implementation (or the loader) will call itself `malloc`
on a first access to a thread local (and recurse in the MI_TLS_MODEL_THREAD_LOCAL).
This goes wrong though if the OS or a library uses the same fixed slot.
- MI_TLS_MODEL_DYNAMIC_WIN32: use a dynamically allocated slot with TlsAlloc. (default on Windows)
Windows has somewhat slow thread locals so by default we use TlsAlloc'd slots which
can be more efficient. First tries to use one of the "direct" first 64 slots which
are the fastest, but falls back to using "expansion" slots when needed (up to 1088 slots).
(If the allocated slot happens to always be under 64 for a particular program,
one might use cmake with `-DMI_WIN_DIRECT_TLS=ON` to skip the expansion slot test in the fast path.)
- MI_TLS_MODEL_DYNAMIC_PTHREADS: use `pthread_getspecific`. (default on OpenBSD, maybe good for Android as well?)
Use pthread local storage. Somewhat slow but can work well depending on the platform.
Each model should define `MI_THEAP_INITASNULL` to signify that the initial value
returned from `_mi_theap_default()` can be `NULL` (instead of the address of the empty heap).
This incurs an extra check in the fast path (but can often be combined in an existing check).
------------------------------------------------------------------------------------------- */
static inline mi_theap_t* _mi_theap_default(void);
static inline mi_theap_t* _mi_theap_cached(void);
#if defined(_WIN32)
#define MI_TLS_MODEL_DYNAMIC_WIN32 1
#elif defined(__APPLE__) && MI_HAS_TLS_SLOT && !defined(__POWERPC__) // macOS on arm64 or x64
// #define MI_TLS_MODEL_DYNAMIC_PTHREADS 1 // also works but a bit slower
#define MI_TLS_MODEL_FIXED_SLOT 1
#define MI_TLS_MODEL_FIXED_SLOT_DEFAULT 108 // seems unused. @apple: it would be great to get 2 official slots for custom allocators :-)
#define MI_TLS_MODEL_FIXED_SLOT_CACHED 109
// we used before __PTK_FRAMEWORK_OLDGC_KEY9 (89) but that seems used now.
// see <https://github.com/rweichler/substrate/blob/master/include/pthread_machdep.h>
#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__ANDROID__)
#define MI_TLS_MODEL_DYNAMIC_PTHREADS 1
// #define MI_TLS_MODEL_DYNAMIC_PTHREADS_DEFAULT_ENTRY_IS_NULL 1
#else
#define MI_TLS_MODEL_THREAD_LOCAL 1
#endif
// Declared this way to optimize register spills and branches
mi_decl_cold mi_decl_noinline mi_theap_t* _mi_theap_empty_get(void);
static inline mi_theap_t* __mi_theap_empty(void) {
#if __GNUC__
__asm(""); // prevent conditional load
return (mi_theap_t*)&_mi_theap_empty;
#else
return _mi_theap_empty_get();
#endif
}
#if MI_TLS_MODEL_THREAD_LOCAL
// Thread local with an initial value (default on Linux). Very efficient.
extern mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_default; // default theap to allocate from
extern mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_cached; // theap from the last used heap
static inline mi_theap_t* _mi_theap_default(void) {
#if defined(MI_TLS_RECURSE_GUARD)
if (mi_unlikely(!_mi_process_is_initialized)) return _mi_theap_empty_get();
#endif
return __mi_theap_default;
}
static inline mi_theap_t* _mi_theap_cached(void) {
return __mi_theap_cached;
}
#elif MI_TLS_MODEL_FIXED_SLOT
// Fixed TLS slot (default on macOS).
#define MI_THEAP_INITASNULL 1
static inline mi_theap_t* _mi_theap_default(void) {
return (mi_theap_t*)mi_prim_tls_slot(MI_TLS_MODEL_FIXED_SLOT_DEFAULT);
}
static inline mi_theap_t* _mi_theap_cached(void) {
return (mi_theap_t*)mi_prim_tls_slot(MI_TLS_MODEL_FIXED_SLOT_CACHED);
}
#elif MI_TLS_MODEL_DYNAMIC_WIN32
// Dynamic TLS slot (default on Windows)
#define MI_THEAP_INITASNULL 1
// We try to use direct slots (64), but can also use the expansion slots (upto 1024 extra available)
// See <https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm> for the offsets.
#if MI_SIZE_SIZE==4
#define MI_TLS_EXPANSION_SLOT (0x0F94 / MI_SIZE_SIZE)
#else
#define MI_TLS_EXPANSION_SLOT (0x1780 / MI_SIZE_SIZE)
#endif
extern mi_decl_hidden size_t _mi_theap_default_slot;
extern mi_decl_hidden size_t _mi_theap_cached_slot;
extern mi_decl_hidden size_t _mi_theap_default_expansion_slot;
extern mi_decl_hidden size_t _mi_theap_cached_expansion_slot;
static inline mi_theap_t* _mi_theap_default(void) {
const size_t slot = _mi_theap_default_slot;
mi_theap_t* theap = (mi_theap_t*)mi_prim_tls_slot(slot);
#if !MI_WIN_DIRECT_TLS
if mi_unlikely(slot==MI_TLS_EXPANSION_SLOT) { // in TlsExpansionSlots ?
if mi_likely(theap!=NULL) { // initialized (on this thread)?
theap = ((mi_theap_t**)theap)[_mi_theap_default_expansion_slot];
}
}
#endif
return theap;
}
static inline mi_theap_t* _mi_theap_cached(void) {
const size_t slot = _mi_theap_cached_slot;
mi_theap_t* theap = (mi_theap_t*)mi_prim_tls_slot(slot);
#if !MI_WIN_DIRECT_TLS
if mi_unlikely(slot==MI_TLS_EXPANSION_SLOT) { // in TlsExpansionSlots ?
if mi_likely(theap!=NULL) { // initialized (on this thread)?
theap = ((mi_theap_t**)theap)[_mi_theap_cached_expansion_slot];
}
}
#endif
return theap;
}
#elif MI_TLS_MODEL_DYNAMIC_PTHREADS
// Dynamic pthread slot on less common platforms. This is not too bad. (default on OpenBSD)
#define MI_THEAP_INITASNULL 1
extern mi_decl_hidden pthread_key_t _mi_theap_default_key;
extern mi_decl_hidden pthread_key_t _mi_theap_cached_key;
static inline mi_theap_t* _mi_theap_default(void) {
#if !MI_TLS_MODEL_DYNAMIC_PTHREADS_DEFAULT_ENTRY_IS_NULL
// we can skip this check if using the initial key will return NULL from pthread_getspecific
if mi_unlikely(_mi_theap_default_key==0) { return NULL; }
#endif
return (mi_theap_t*)pthread_getspecific(_mi_theap_default_key);
}
static inline mi_theap_t* _mi_theap_cached(void) {
#if !MI_TLS_MODEL_DYNAMIC_PTHREADS_DEFAULT_ENTRY_IS_NULL
// we can skip this check if using the initial key will return NULL from pthread_getspecific
if mi_unlikely(_mi_theap_cached_key==0) { return NULL; }
#endif
return (mi_theap_t*)pthread_getspecific(_mi_theap_cached_key);
}
#else
#error "no TLS model is defined for this platform?"
#endif
// Check if a thread is initialized (without using a thread-local if using fixed slots)
static inline bool _mi_thread_is_initialized(void) {
return (mi_theap_is_initialized(_mi_theap_default()));
}
// Get (and possible create) the theap belonging to a heap
// We cache the last accessed theap in `_mi_theap_cached` for better performance.
static inline mi_theap_t* _mi_heap_theap(const mi_heap_t* heap) {
mi_theap_t* theap = _mi_theap_cached();
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL && _mi_theap_heap(theap)==heap) return theap;
#else
if mi_likely(_mi_theap_heap(theap)==heap) return theap;
#endif
return _mi_heap_theap_get_or_init(heap);
}
// Get the theap belonging to a heap without creating in if it is not yet initialized.
static inline mi_theap_t* _mi_heap_theap_peek(const mi_heap_t* heap) {
mi_theap_t* theap = _mi_theap_cached();
#if MI_THEAP_INITASNULL
if mi_unlikely(theap==NULL || _mi_theap_heap(theap)!=heap)
#else
if mi_unlikely(_mi_theap_heap(theap)!=heap)
#endif
{
theap = _mi_heap_theap_get_peek(heap); // don't update the cache on a query (?)
}
mi_assert(theap==NULL || _mi_theap_heap(theap)==heap);
return theap;
}
// Find the associated theap or NULL if it does not exist (during shutdown)
// Should be fast as it is called in `free.c:mi_free_try_collect`.
static inline mi_theap_t* _mi_page_associated_theap_peek(mi_page_t* page) {
mi_heap_t* const heap = page->heap;
mi_theap_t* theap;
if mi_likely(heap==NULL) { theap = __mi_theap_main; } // note: on macOS accessing the thread_local can cause allocation during thread shutdown (and reinitialize the thread)!
else { theap = _mi_heap_theap_peek(heap); }
mi_assert_internal(theap==NULL || _mi_thread_id()==theap->tld->thread_id);
return theap;
}
#endif // MI_PRIM_H
+5 -5
View File
@@ -133,18 +133,18 @@ defined, undefined, or not accessible at all:
#endif
#if MI_PADDING
#if MI_PADDING_CHECK_BYTES && !MI_GUARDED
#define mi_track_malloc(p,reqsize,zero) \
if ((p)!=NULL) { \
do { if ((p)!=NULL) { \
mi_assert_internal(mi_usable_size(p)==(reqsize)); \
mi_track_malloc_size(p,reqsize,reqsize,zero); \
}
} } while(0)
#else
#define mi_track_malloc(p,reqsize,zero) \
if ((p)!=NULL) { \
do { if ((p)!=NULL) { \
mi_assert_internal(mi_usable_size(p)>=(reqsize)); \
mi_track_malloc_size(p,reqsize,mi_usable_size(p),zero); \
}
} } while(0)
#endif
#endif // MI_TRACK_H
+225 -89
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -24,12 +24,13 @@ terms of the MIT license. A copy of the license can be found in the file
#include <mimalloc-stats.h>
#include <mimalloc-profile.h>
#include <stddef.h> // ptrdiff_t
#include <stdint.h> // uintptr_t, uint16_t, etc
#include <stdbool.h> // bool
#include <limits.h> // SIZE_MAX etc.
#include <errno.h> // error codes
#include "bits.h" // size defines (MI_INTPTR_SIZE etc), bit operations
#include "bits.h" // size defines (MI_SIZE_SIZE etc), bit operations
#include "atomic.h" // _Atomic primitives
// Minimal alignment necessary. On most platforms 16 bytes are needed
@@ -38,6 +39,9 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_MAX_ALIGN_SIZE 16 // sizeof(max_align_t)
#endif
#if MI_MAX_ALIGN_SIZE < MI_INTPTR_SIZE
#error MI_MAX_ALIGN_SIZE must be at least MI_INTPTR_SIZE
#endif
// ------------------------------------------------------
// Variants
@@ -51,15 +55,15 @@ terms of the MIT license. A copy of the license can be found in the file
// #define MI_TRACK_ASAN 1
// #define MI_TRACK_ETW 1
// Define MI_STAT as 1 to maintain statistics; set it to 2 to have detailed statistics (but costs some performance).
// #define MI_STAT 1
// Define MI_STATS as 1 to maintain statistics; set it to 2 to have detailed statistics (but costs some performance).
// #define MI_STATS 1
// Define MI_SECURE to enable security mitigations
// #define MI_SECURE 1 // guard pages around meta data, randomize arena allocation addresses (like ASLR), abort on detected meta data corruption
// #define MI_SECURE 2 // randomize relative allocation addresses (within mimalloc pages)
// #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free)
// #define MI_SECURE 4 // checks for double free (may be more expensive) (`-DMI_SECURE=ON`)
// #define MI_SECURE 5 // guard page at the end of each mimalloc page (expensive!) (`-DMI_SECURE_FULL=ON`)
// #define MI_SECURE 1 // check invalid pointer free, guard pages around meta data, randomize arena allocation addresses (like ASLR), abort on detected meta data corruption
// #define MI_SECURE 2 // randomize relative allocation addresses within mimalloc pages
// #define MI_SECURE 3 // check buffer overflow, check double free, encode free lists (and detect corrupted free lists)
// #define MI_SECURE 4 // same as level 3 for now (`-DMI_SECURE=ON`)
// #define MI_SECURE 5 // guard page at the end of each mimalloc page (expensive!) (`-DMI_SECURE_FULL=ON`), and byte-precise buffer overflow checks.
#if !defined(MI_SECURE)
#define MI_SECURE 0
@@ -77,29 +81,49 @@ terms of the MIT license. A copy of the license can be found in the file
#endif
#endif
// Statistics (0=only essential, 1=normal, 2=more fine-grained (expensive) tracking)
#ifndef MI_STAT
// Statistics (0=only essential, 1=detailed (fast, can be enabled always), 2=slightly more expensive tracking of precise requested bytes)
#ifndef MI_STATS
#if (MI_DEBUG>0)
#define MI_STAT 2
#define MI_STATS 2
#else
#define MI_STAT 0
#define MI_STATS 1
#endif
#endif
// Enable profiling support (0=off, 1=fast, can be enabled always, 2=allow fine-grained sample rates(<64 KiB), a tad more expensive)
#ifndef MI_PROFILE
#define MI_PROFILE 1
#endif
// Enable guard pages behind objects of a certain size (set by the MIMALLOC_GUARDED_MIN/MAX/SAMPLE_RATE options)
#if !defined(MI_GUARDED) && MI_DEBUG && !defined(NDEBUG) && !MI_PAGE_META_ALIGNED_FREE_SMALL
#if !defined(MI_GUARDED) && MI_DEBUG && !defined(NDEBUG) && !MI_OPT_FREE_SMALL
#define MI_GUARDED 1
#endif
// Reserve extra padding at the end of each block to be more resilient against theap block overflows.
// The padding can detect buffer overflow on free.
#if !defined(MI_PADDING) && (MI_SECURE>=3 || MI_DEBUG>=1 || (MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_TRACK_ETW))
#define MI_PADDING 1
// For profiling or guarded pages we need to sample every once in a while. (0=no sampling, 1=fast, 2=allow fine grained sample rates(<64 KiB), a tad more expensive)
#ifndef MI_SAMPLE
#if MI_PROFILE>1 || MI_GUARDED>1
#define MI_SAMPLE 2
#elif MI_PROFILE || MI_GUARDED
#define MI_SAMPLE 1
#else
#define MI_SAMPLE 0
#endif
#endif
// Check padding bytes; allows byte-precise buffer overflow detection
#if !defined(MI_PADDING_CHECK) && MI_PADDING && (MI_SECURE>=3 || MI_DEBUG>=1)
#define MI_PADDING_CHECK 1
// Reserve extra padding at the end of each block to be more resilient against theap block overflows.
// The padding can detect heap-block overflow on free, and provides byte-precise `mi_usable_size`.
#if !defined(MI_PADDING)
#if (MI_SECURE>=3 || MI_DEBUG>=1 || (MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_TRACK_ETW))
#define MI_PADDING 1
#else
#define MI_PADDING 0
#endif
#endif
// Check for byte-precise buffer overflow?
#if !defined(MI_PADDING_CHECK_BYTES) && MI_PADDING && (MI_SECURE>=5 || MI_DEBUG>=1)
#define MI_PADDING_CHECK_BYTES 1
#endif
@@ -109,6 +133,17 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_ENCODE_FREELIST 1
#endif
// Deprecated (double-free is now checked with padding)
// #if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0))
// #define MI_CHECK_DOUBLE_FREE 1
// #endif
#if MI_SECURE>=4 || MI_PADDING || (MI_FREE_IS_CHECKED && MI_FREE_USE_PAGEMAP)
#define MI_PAGE_KEY_COUNT 2
#else
#define MI_PAGE_KEY_COUNT 1
#endif
// Enable large pages for objects between 64KiB and 512KiB.
// This should perhaps be disabled by default as for many workloads the block sizes above 64 KiB
// are quite random which can lead to too many partially used large pages (but see issue #1104).
@@ -116,10 +151,9 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_ENABLE_LARGE_PAGES 1
#endif
// Place page meta info at the start of the page area or keep it separate?
// Separate keeps the page info at the arena start (default) which is more secure
// and reduces wasted space due to alignment and block sizes.
// (but also reserves more memory up front (about 2MiB per GiB))
// Place page meta info at the start of the page area or keep it separate?
// Separate keeps the page info at the arena start (default) which is more secure
// and reduces wasted space due to alignment and block sizes.
#if !defined(MI_PAGE_META_IS_SEPARATED)
#if MI_PAGE_MAP_FLAT
#define MI_PAGE_META_IS_SEPARATED 0
@@ -128,19 +162,38 @@ terms of the MIT license. A copy of the license can be found in the file
#endif
#endif
// We can choose to page meta info aligned at the start of every MI_PAGE_META_ALIGNED_CHUNKS
// This can be used to have a faster `mi_free(_small)` as we can avoid a page_map lookup.
// This only works if valid pointers are passed to `mi_free` though. However, checked
// free `mi_cfree` can still use the page map to validate pointers.
#if !MI_FREE_IS_CHECKED && !MI_FREE_USE_PAGEMAP
#if MI_PAGE_META_IS_SEPARATED
#define MI_PAGE_META_IS_ALIGNED 1
#define MI_PAGE_META_ALIGNED_CHUNKS MI_SIZE_SIZE
#else
#warning "cannot optimize free with alignment since the page meta data is not separated (due to MI_PAGE_MAP_FLAT?)"
#endif
#endif
// We can choose to only put page info of small pages at the start of the page area.
// This can be used to have a slightly faster `mi_free_small` function for specialized
// cases (like language runtime systems).
#if !defined(MI_PAGE_META_ALIGNED_FREE_SMALL)
#define MI_PAGE_META_ALIGNED_FREE_SMALL 0
#if !defined(MI_PAGE_META_SMALL_IS_ALIGNED)
#if defined(MI_OPT_FREE_SMALL) && MI_OPT_FREE_SMALL==0
#define MI_PAGE_META_SMALL_IS_ALIGNED 0
#elif (MI_OPT_FREE_SMALL || MI_PAGE_META_IS_ALIGNED) && !MI_SECURE && !MI_GUARDED // guarded can put small allocations into big blocks in a medium page (which would make `mi_free_small` fail)
#define MI_PAGE_META_SMALL_IS_ALIGNED 1
#else
#define MI_PAGE_META_SMALL_IS_ALIGNED 0
#endif
#endif
// Configuration checks
#if !MI_PAGE_META_IS_SEPARATED && MI_SECURE
#error "secure mode should use separated page infos"
#endif
#if MI_PAGE_META_ALIGNED_FREE_SMALL && MI_SECURE
#error "secure mode cannot use MI_PAGE_META_ALIGNED_FREE_SMALL"
#if MI_PAGE_META_SMALL_IS_ALIGNED && MI_SECURE
#error "secure mode cannot use MI_OPT_FREE_SMALL (MI_PAGE_META_SMALL_IS_ALIGNED)"
#endif
#if MI_PAGE_META_IS_SEPARATED && MI_PAGE_MAP_FLAT
#error "cannot have a flat page map with separated page infos"
@@ -149,7 +202,6 @@ terms of the MIT license. A copy of the license can be found in the file
#warning "mimalloc assertions enabled in a release build"
#endif
// --------------------------------------------------------------
// Sizes of internal data-structures
// (comments specify sizes on 64-bit, usually 32-bit is halved)
@@ -159,14 +211,14 @@ terms of the MIT license. A copy of the license can be found in the file
#ifndef MI_ARENA_SLICE_SHIFT
#ifdef MI_SMALL_PAGE_SHIFT // backward compatibility
#define MI_ARENA_SLICE_SHIFT MI_SMALL_PAGE_SHIFT
#elif MI_SECURE>=5 && __APPLE__ && MI_ARCH_ARM64
#elif MI_SECURE>=5 && ((__APPLE__ && MI_ARCH_ARM64) || (defined(PAGE_SIZE) && PAGE_SIZE >= 16*MI_KiB))
#define MI_ARENA_SLICE_SHIFT (17) // 128 KiB to not waste too much due to 16 KiB guard pages
#else
#define MI_ARENA_SLICE_SHIFT (13 + MI_SIZE_SHIFT) // 64 KiB (32 KiB on 32-bit)
#endif
#endif
#if MI_ARENA_SLICE_SHIFT < 12
#error Arena slices should be at least 4KiB
#if MI_ARENA_SLICE_SHIFT < 13
#error Arena slices should be at least 8KiB
#endif
#ifndef MI_BCHUNK_BITS_SHIFT
@@ -182,6 +234,7 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_BCHUNK_BITS (1 << MI_BCHUNK_BITS_SHIFT) // sub-bitmaps in arena's are "bchunks" of 512 bits
#define MI_ARENA_SLICE_SIZE (MI_ZU(1) << MI_ARENA_SLICE_SHIFT) // arena's allocate in slices of 64 KiB
#define MI_ARENA_SLICE_ALIGN (MI_ARENA_SLICE_SIZE)
#define MI_ARENA_CHUNK_SIZE (MI_BCHUNK_BITS * MI_ARENA_SLICE_SIZE)
#define MI_ARENA_MIN_OBJ_SLICES (1)
#define MI_ARENA_MAX_CHUNK_OBJ_SLICES (MI_BCHUNK_BITS) // 32 MiB (or 8 MiB on 32-bit)
@@ -206,11 +259,18 @@ terms of the MIT license. A copy of the license can be found in the file
#define MI_BIN_COUNT (MI_BIN_FULL+1)
// We never allocate more than PTRDIFF_MAX (see also <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
#define MI_MAX_ALLOC_SIZE PTRDIFF_MAX
#define MI_MAX_ALLOC_SIZE PTRDIFF_MAX
// Minimal commit for a page on-demand commit (should be >= OS page size)
#define MI_PAGE_MIN_COMMIT_SIZE MI_ARENA_SLICE_SIZE
// Minimal commit for a page on-demand commit
#define MI_PAGE_MIN_COMMIT_SIZE (16*MI_KiB) /* MI_ARENA_SLICE_SIZE */
#if MI_PAGE_META_IS_ALIGNED
#define MI_PAGE_META_ALIGNED_COUNT (MI_PAGE_META_ALIGNED_CHUNKS * MI_BCHUNK_BITS)
#define MI_PAGE_META_ALIGNMENT (MI_PAGE_META_ALIGNED_COUNT * MI_ARENA_SLICE_SIZE) // 256 MiB (32 MiB on 32-bit)
#define MI_ARENA_ALIGNMENT MI_PAGE_META_ALIGNMENT
#else
#define MI_ARENA_ALIGNMENT MI_ARENA_SLICE_ALIGN
#endif
// ------------------------------------------------------
// Arena's are large reserved areas of memory allocated from
@@ -248,15 +308,14 @@ typedef struct mi_subproc_s mi_subproc_t;
// Memory can reside in arena's, direct OS allocated, meta-data pages, or statically allocated.
// The memid keeps track of this.
typedef enum mi_memkind_e {
MI_MEM_NONE, // not allocated
MI_MEM_NONE, // not allocated (or static)
MI_MEM_EXTERNAL, // not owned by mimalloc but provided externally (via `mi_manage_os_memory` for example)
MI_MEM_STATIC, // allocated in a static area and should not be freed (the initial main theap data for example (`init.c`))
MI_MEM_META, // allocated with the meta data allocator (`arena-meta.c`)
MI_MEM_OS, // allocated from the OS
MI_MEM_OS_HUGE, // allocated as huge OS pages (usually 1GiB, pinned to physical memory)
MI_MEM_OS_REMAP, // allocated in a remapable area (i.e. using `mremap`)
MI_MEM_ARENA, // allocated from an arena (the usual case) (`arena.c`)
MI_MEM_HEAP_MAIN // allocated in the main heap (for theaps)
MI_MEM_MALLOC // allocated with mi_malloc
} mi_memkind_t;
static inline bool mi_memkind_is_os(mi_memkind_t memkind) {
@@ -267,6 +326,7 @@ static inline bool mi_memkind_needs_no_free(mi_memkind_t memkind) {
return (memkind <= MI_MEM_STATIC);
}
typedef struct mi_meta_page_s mi_meta_page_t;
typedef struct mi_memid_os_info {
void* base; // actual base address of the block (used for offset aligned allocations)
@@ -280,17 +340,16 @@ typedef struct mi_memid_arena_info {
uint32_t slice_count; // allocated slices
} mi_memid_arena_info_t;
typedef struct mi_memid_meta_info {
void* meta_page; // meta-page that contains the block
uint32_t block_index; // block index in the meta-data page
uint32_t block_count; // allocated blocks
} mi_memid_meta_info_t;
typedef struct mi_memid_malloc_info {
void* base; // returned pointer
size_t size; // allocated size
} mi_memid_malloc_info_t;
typedef struct mi_memid_s {
union {
mi_memid_os_info_t os; // only used for MI_MEM_OS
mi_memid_arena_info_t arena; // only used for MI_MEM_ARENA
mi_memid_meta_info_t meta; // only used for MI_MEM_META
mi_memid_os_info_t os; // only used for MI_MEM_OS(_HUGE/_REMAP)
mi_memid_arena_info_t arena; // only used for MI_MEM_ARENA
mi_memid_malloc_info_t malloc; // only used for MI_MEM_MALLOC
} mem;
mi_memkind_t memkind;
bool is_pinned; // `true` if we cannot decommit/reset/protect in this memory (e.g. when allocated using large (2Mib) or huge (1GiB) OS pages)
@@ -298,6 +357,8 @@ typedef struct mi_memid_s {
bool initially_zero; // `true` if the memory was originally zero initialized
} mi_memid_t;
#define MI_MEMID_INIT(kind) {{{NULL,0}}, kind, true /* pinned */, true /* committed */, false /* zero */ }
#define MI_MEMID_STATIC MI_MEMID_INIT(MI_MEM_STATIC)
static inline bool mi_memid_is_os(mi_memid_t memid) {
return mi_memkind_is_os(memid.memkind);
@@ -335,6 +396,7 @@ typedef struct mi_block_s {
#define MI_PAGE_IN_FULL_QUEUE MI_ZU(0x01)
#define MI_PAGE_HAS_INTERIOR_POINTERS MI_ZU(0x02)
#define MI_PAGE_FLAG_MASK MI_ZU(0x03)
#define MI_PAGE_FLAG_BITS (2)
typedef size_t mi_page_flags_t;
// There are two special threadid's: 0 for pages that are abandoned (and not in a theap queue),
@@ -342,7 +404,8 @@ typedef size_t mi_page_flags_t;
// in an arena (in `mi_heap_t.arena_pages.pages_abandoned`) so these can be quickly found for reuse.
// Abandoning partially used pages allows for sharing of this memory between threads (in particular if threads are blocked)
#define MI_THREADID_ABANDONED MI_ZU(0)
#define MI_THREADID_ABANDONED_MAPPED (MI_PAGE_FLAG_MASK + 1)
#define MI_THREADID_ABANDONED_MAPPED (MI_ZU(1) << MI_PAGE_FLAG_BITS)
#define MI_THREADID_DETACHED (MI_ZU(2) << MI_PAGE_FLAG_BITS)
// Thread free list.
// Points to a list of blocks that are freed by other threads.
@@ -351,6 +414,29 @@ typedef size_t mi_page_flags_t;
// This way we can push a block on the thread free list and try to claim ownership atomically in `free.c:mi_free_block_mt`.
typedef uintptr_t mi_thread_free_t;
// We store the currently used block count together with the total malloc call count as 16-bit numbers.
// This is done for better codegen `mi_malloc/mi_free` (where we can increment both at once as `used_alloc += 0x10001` for example).
// The `last_used` is the `used` count since the statistics are last updated; on 32-bit platforms this
// is a separate field in `mi_page_t` but on 64-bit we use the upper 32-bits to store it.
// We need the `alloc_count` and `last_used` to efficiently calculate allocation and free statistics even
// in a release build; this way we can update the stats in the slow path (`_mi_page_update_stats`).
typedef union mi_used_s {
size_t used_alloc; // used + alloc_count
// the following struct is unused but nice for debugging
struct {
uint16_t used_count;
uint16_t alloc_count;
#if MI_SIZE_SIZE >= 8
uint16_t last_used;
uint16_t last_alloc;
#endif
} debug_le;
} mi_used_t;
static inline size_t mi_xused_used_count(mi_used_t xused) { return (xused.used_alloc & 0xFFFF); }
static inline size_t mi_xused_alloc_count(mi_used_t xused) { return ((xused.used_alloc>>16) & 0xFFFF); }
static inline mi_used_t mi_xused_used_reset(mi_used_t xused) { xused.used_alloc = xused.used_alloc & ~0xFFFF; return xused; }
// A page contains blocks of one specific size (`block_size`).
// Each page has three list of free blocks:
// `free` for blocks that can be allocated,
@@ -376,44 +462,48 @@ typedef uintptr_t mi_thread_free_t;
// that case the `xthreadid` is 0 or 4 (4 is for abandoned pages that
// are in the `pages_abandoned` lists of an arena, these are called "mapped" abandoned pages).
// - page flags are in the bottom 3 bits of `xthread_id` for the fast path in `mi_free`.
// - The layout is optimized for `free.c:mi_free` and `alloc.c:mi_page_alloc`
// - Using `uint16_t` does not seem to slow things down
// - The layout below is optimized for `free.c:mi_free` and `alloc.c:mi_page_alloc`
typedef struct mi_page_s {
typedef struct mi_page_s {
#if (MI_PAGE_META_IS_ALIGNED)
_Atomic(struct mi_page_s*) self; // points to the actual page info (for pages that span multiple slices)
#endif
_Atomic(mi_threadid_t) xthread_id; // thread this page belongs to. (= `theap->thread_id (or 0 or 4 if abandoned) | page_flags`)
mi_block_t* free; // list of available free blocks (`malloc` allocates from this list)
uint16_t used; // number of blocks in use (including blocks in `thread_free`)
mi_used_t xused; // number of blocks in use (including blocks in `thread_free`) (and the allocated count for statistics)
#if MI_SIZE_SIZE < 8
uint16_t xlast_used; // for statistics; on 64-bit platforms it is in bits 32..47 of xused.
uint16_t xlast_alloc; // for sampling; on 64-bit platforms it is in bits 48..63 of xused.
#endif
mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`)
size_t block_size; // const: size available in each block (always `>0`)
size_t page_offset; // const: relative offset from the page to the start of the blocks
uint16_t capacity; // number of blocks committed
uint16_t reserved; // number of blocks reserved in memory
uint16_t slice_pcommitted; // committed size in OS page sizes relative to the first arena slice of the page data (or 0 if the page is fully committed already)
uint8_t retire_expire; // expiration count for retired blocks
bool free_is_zero; // `true` if the blocks in the free list are zero initialized
mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`)
// next cache line
_Atomic(mi_thread_free_t) xthread_free; // list of deferred free blocks freed by other threads (= `mi_block_t* | (1 if owned)`)
size_t block_size; // const: size available in each block (always `>0`)
uint8_t* page_start; // const: start of the blocks
#if (MI_ENCODE_FREELIST || MI_PADDING)
uintptr_t keys[2]; // const: two random keys to encode the free lists (see `_mi_block_next`) or padding canary
#endif
mi_theap_t* theap; // the theap owning this page (may not be valid or NULL for abandoned pages)
mi_heap_t* heap; // const: the heap owning this page
struct mi_page_s* next; // next page owned by the theap with the same `block_size`
struct mi_page_s* prev; // previous page owned by the theap with the same `block_size`
size_t slice_committed; // committed size relative to the first arena slice of the page data (or 0 if the page is fully committed already)
mi_memid_t memid; // const: provenance of the page memory
#if (MI_ENCODE_FREELIST || MI_PADDING)
uintptr_t keys[MI_PAGE_KEY_COUNT]; // const: one or two random keys to encode the free lists (see `_mi_block_next`) or padding canary
#endif
} mi_page_t;
// ------------------------------------------------------
// Object sizes
// ------------------------------------------------------
#define MI_PAGE_ALIGN MI_ARENA_SLICE_ALIGN // pages must be aligned on this for the page map.
#define MI_PAGE_ALIGN MI_ARENA_SLICE_ALIGN // page area's must be aligned on this for the page map.
#define MI_PAGE_MIN_START_BLOCK_ALIGN MI_MAX_ALIGN_SIZE // minimal block alignment for the first block in a page (16b)
#define MI_PAGE_MAX_START_BLOCK_ALIGN2 (4*MI_KiB) // maximal block alignment for "power of 2"-sized blocks (such that we guarantee natural alignment)
#define MI_PAGE_OSPAGE_BLOCK_ALIGN2 (4*MI_KiB) // also aligns any multiple of this size to avoid TLB misses.
@@ -423,7 +513,7 @@ typedef struct mi_page_s {
#define MI_SMALL_MAX_OBJ_SIZE ((MI_SMALL_PAGE_SIZE-MI_PAGE_OSPAGE_BLOCK_ALIGN2)/6) // = 10 KiB
#if MI_ENABLE_LARGE_PAGES
#define MI_MEDIUM_MAX_OBJ_SIZE ((MI_MEDIUM_PAGE_SIZE-MI_PAGE_OSPAGE_BLOCK_ALIGN2)/6) // ~ 84 KiB
#define MI_LARGE_MAX_OBJ_SIZE (MI_LARGE_PAGE_SIZE/8) // <= 512 KiB // note: this must be a nice power of 2 or we get rounding issues with `_mi_bin`
#define MI_LARGE_MAX_OBJ_SIZE (MI_LARGE_PAGE_SIZE/8) // <= 512 KiB. note: this must be a nice power of 2 or we get rounding issues with `_mi_bin`
#else
#define MI_MEDIUM_MAX_OBJ_SIZE (MI_MEDIUM_PAGE_SIZE/8) // <= 64 KiB
#define MI_LARGE_MAX_OBJ_SIZE MI_MEDIUM_MAX_OBJ_SIZE // note: this must be a nice power of 2 or we get rounding issues with `_mi_bin`
@@ -434,6 +524,23 @@ typedef struct mi_page_s {
#error "mimalloc internal: define more bins"
#endif
// static invariant: MI_MAX_SINGLETON_BIN >= _mi_bin(MI_LARGE_MAX_OBJ_SIZE) (See init.c for the size bins)
#if (MI_LARGE_MAX_OBJ_WSIZE <= 8192) // 64 KiB
#define MI_MAX_SINGLETON_BIN (48)
#elif (MI_LARGE_MAX_OBJ_WSIZE <= 32768) // 256KiB
#define MI_MAX_SINGLETON_BIN (56)
#elif (MI_LARGE_MAX_OBJ_WSIZE <= 65536) // 512KiB
#define MI_MAX_SINGLETON_BIN (60)
#else
#define MI_MAX_SINGLETON_BIN MI_BIN_HUGE
#endif
// The small object max size must be larger than twice the small size max minus one,
// such that any aligned allocation with `alignment <= size <= MI_SMALL_SIZE_MAX` will
// be allocated in a small page (so `mi_free_csize` can delegate correctly to `mi_free_small`.
#if MI_SMALL_MAX_OBJ_SIZE <= (2*(MI_SMALL_WSIZE_MAX * MI_SIZE_SIZE)-1)
#error "mimalloc internal: the small object max size is too small"
#endif
// ------------------------------------------------------
// Page kinds
@@ -488,10 +595,10 @@ typedef struct mi_random_cxt_s {
#if MI_PADDING
typedef struct mi_padding_s {
uint32_t canary; // encoded block value to check validity of the padding (in case of overflow)
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
uint32_t delta; // padding bytes before the block. (mi_full_usable_size(p) - delta == exact allocated bytes)
} mi_padding_t;
#define MI_PADDING_SIZE (sizeof(mi_padding_t))
#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE)
#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_SIZE_SIZE - 1) / MI_SIZE_SIZE)
#else
#define MI_PADDING_SIZE 0
#define MI_PADDING_WSIZE 0
@@ -502,11 +609,34 @@ typedef struct mi_padding_s {
// A thread-local heap ("theap") owns a set of thread-local pages.
struct mi_theap_s {
// put in front for fast small allocations
mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.
// less frequently accessed fields
mi_tld_t* tld; // thread-local data
_Atomic(mi_heap_t*) heap; // the heap this theap belongs to.
_Atomic(size_t) refcount; // reference count
_Atomic(mi_subproc_t*)subproc; // subproc this belongs too (always `subproc == heap->subproc` but needed for safe destruction)
_Atomic(size_t) refcount; // reference count (needed for safe heap destroy)
// config
long page_full_retain; // how many full pages can be retained per queue (before abandoning them)
bool allow_page_reclaim; // `true` if this theap can reclaim abandoned pages
bool allow_page_abandon; // `true` if this theap can abandon pages to reduce memory footprint
bool is_detached; // `true` if `tld->thread_id == MI_THREADID_DETACHED`
// sampling
size_t sample_countdown; // sample countdown in requested bytes (don't change the field order; see `internal.h:_mi_theap_get_free_small_page`)
size_t sample_rate; // current sampling rate in requested bytes (or 0 to disable) (for profiling and guarded mode)
uint64_t sample_requested; // total allocated/requested bytes since the last sample
size_t profile_sample_rate; // sampling rate in requested bytes for profiling
size_t profile_sample_countdown; // countdown in requested bytes for profiling
size_t guarded_sample_rate; // sampling rate in requested bytes for guarded objects
size_t guarded_sample_countdown; // countdown in requested bytes for guarded objects
size_t guarded_size_min; // minimal size for guarded objects
size_t guarded_size_max; // maximal size for guarded objects
// stats
unsigned long long heartbeat; // monotonic heartbeat count
uintptr_t cookie; // random cookie to verify pointers (see `_mi_ptr_cookie`)
mi_random_ctx_t random; // random number context used for secure allocation
size_t page_count; // total number of pages in the `pages` queues.
size_t page_retired_min; // smallest retired index (retired pages are fully free, but still in the page queues)
@@ -515,21 +645,13 @@ struct mi_theap_s {
long generic_count; // how often is `_mi_malloc_generic` called?
long generic_collect_count; // how often is `_mi_malloc_generic` called without collecting?
// theaps belong to heaps and threads
mi_theap_t* tnext; // list of theaps in this thread
mi_theap_t* tprev;
mi_theap_t* tprev;
mi_theap_t* hnext; // list of theaps of the owning `heap`
mi_theap_t* hprev;
long page_full_retain; // how many full pages can be retained per queue (before abandoning them)
bool allow_page_reclaim; // `true` if this theap should not reclaim abandoned pages
bool allow_page_abandon; // `true` if this theap can abandon pages to reduce memory footprint
#if MI_GUARDED
size_t guarded_size_min; // minimal size for guarded objects
size_t guarded_size_max; // maximal size for guarded objects
size_t guarded_sample_rate; // sample rate (set to 0 to disable guarded pages)
size_t guarded_sample_count; // current sample count (counting down to 0)
#endif
mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.
// page queues
mi_page_queue_t pages[MI_BIN_COUNT]; // queue of pages for each size class (or "bin")
mi_memid_t memid; // provenance of the theap struct itself (meta or os)
mi_stats_t stats; // thread-local statistics
@@ -566,13 +688,15 @@ typedef struct mi_heap_s {
mi_theap_t* theaps; // list of all thread-local theaps belonging to this heap (using the `hnext`/`hprev` fields)
mi_lock_t theaps_lock; // lock for the theaps list operations
_Atomic(mi_profiler_t*) profiler;
_Atomic(size_t) abandoned_count[MI_BIN_COUNT]; // total count of abandoned pages in this heap
mi_page_t* os_abandoned_pages; // list of pages that are OS allocated and not in an arena
mi_lock_t os_abandoned_pages_lock; // lock for the os abandoned pages list (this lock protects list operations)
_Atomic(mi_arena_pages_t*) arena_pages[MI_MAX_ARENAS]; // track owned and abandoned pages in the arenas (entries can be NULL)
mi_lock_t arena_pages_lock; // lock to update the arena_pages array
mi_memid_t memid; // provenance of the heap memory
mi_stats_t stats; // statistics for this heap; periodically updated by merging from each theap
} mi_heap_t;
@@ -590,6 +714,7 @@ struct mi_subproc_s {
size_t subproc_seq; // unique id for sub-processes
mi_subproc_t* next; // list of all sub-processes
mi_subproc_t* prev;
_Atomic(mi_meta_page_t*) meta_pages; // meta data pages
_Atomic(size_t) arena_count; // current count of arena's
_Atomic(mi_arena_t*) arenas[MI_MAX_ARENAS]; // arena's of this sub-process
@@ -597,16 +722,22 @@ struct mi_subproc_s {
mi_decl_align(8) // needed on some 32-bit platforms
_Atomic(int64_t) purge_expire; // expiration is set if any arenas can be purged
_Atomic(mi_heap_t*) heap_main; // main heap for this sub process
_Atomic(mi_heap_t*) heap_main; // main heap for this sub process
mi_heap_t* heaps; // heaps belonging to this sub-process
mi_lock_t heaps_lock;
mi_theap_t* theap_meta; // detached theap for allocating meta-data
mi_lock_t theap_meta_lock; // all allocations in theap_meta need a lock
_Atomic(size_t) thread_count; // current threads associated with this sub-process
_Atomic(size_t) thread_total_count; // total created threads associated with this sub-process
_Atomic(size_t) heap_count; // current heaps in this sub-process (== |heaps|)
_Atomic(size_t) heap_total_count; // total created heaps in this sub-process
_Atomic(mi_profiler_t*) profiler;
mi_memid_t memid; // provenance of this memory block (meta or static)
mi_subproc_t* parent; // subproc in which this one was allocated
mi_decl_align(8) // needed on some 32-bit platforms
mi_stats_t stats; // subprocess statistics; updated for arena/OS stats like committed,
// and otherwise merged with heap stats when those are deleted
@@ -646,18 +777,18 @@ struct mi_tld_s {
to reserve large arenas upfront and be able to reuse the memory more effectively.
-----------------------------------------------------------------------------*/
#define MI_ARENA_BIN_COUNT (MI_BIN_COUNT)
#define MI_ARENA_BIN_COUNT (MI_MAX_SINGLETON_BIN+1)
#define MI_ARENA_MIN_SIZE (MI_BCHUNK_BITS * MI_ARENA_SLICE_SIZE) // 32 MiB (or 8 MiB on 32-bit)
#define MI_ARENA_MAX_SIZE (MI_BITMAP_MAX_BIT_COUNT * MI_ARENA_SLICE_SIZE)
#define MI_ARENA_MAX_SIZE (MI_BITMAP_MAX_BIT_COUNT * MI_ARENA_SLICE_SIZE) // 16 GiB
typedef struct mi_bitmap_s mi_bitmap_t; // atomic bitmap (defined in `src/bitmap.h`)
typedef struct mi_bbitmap_s mi_bbitmap_t; // atomic binned bitmap (defined in `src/bitmap.h`)
typedef struct mi_arena_pages_s {
struct mi_arena_pages_s {
mi_bitmap_t* pages; // all registered pages (abandoned and owned)
mi_bitmap_t* pages_abandoned[MI_ARENA_BIN_COUNT]; // abandoned pages per size bin (a set bit means the start of the page)
// followed by the bitmaps (whose siz`es depend on the arena size)
} mi_arena_pages_t;
};
// A memory arena
@@ -665,6 +796,7 @@ typedef struct mi_arena_s {
mi_memid_t memid; // provenance of the memory area
mi_subproc_t* subproc; // subprocess this arena belongs to (`this 'element-of' this->subproc->arenas`)
size_t arena_idx; // index in the arenas array
void* start; // actual start of the arena area (the arena_s info may come later due to guard pages etc.)
size_t slice_count; // total size of the area in arena slices (of `MI_ARENA_SLICE_SIZE`)
size_t info_slices; // initial slices reserved for the arena bitmaps
@@ -713,6 +845,10 @@ typedef struct mi_arena_s {
#ifndef EOVERFLOW // count*size overflow
#define EOVERFLOW (75)
#endif
#ifndef ENOENT // environment variable not found
#define ENOENT (2)
#endif
/* -----------------------------------------------------------
Debug constants
+124 -118
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -7,7 +7,7 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim.h" // _mi_theap_default
#include "mimalloc/prim-tls.h" // _mi_theap_default
#include <string.h> // memset
@@ -18,7 +18,7 @@ terms of the MIT license. A copy of the license can be found in the file
static bool mi_malloc_is_naturally_aligned( size_t size, size_t alignment ) {
// certain blocks are always allocated at a certain natural alignment.
// (see also `arena.c:mi_arenas_page_alloc_fresh`).
mi_assert_internal(_mi_is_power_of_two(alignment) && (alignment > 0));
mi_assert_internal(mi_alignment_is_valid(alignment));
if (alignment > size) return false;
const size_t bsize = mi_good_size(size);
const bool ok = (bsize <= MI_PAGE_MAX_START_BLOCK_ALIGN2 && _mi_is_power_of_two(bsize)) || // power-of-two under N
@@ -27,8 +27,8 @@ static bool mi_malloc_is_naturally_aligned( size_t size, size_t alignment ) {
return ok;
}
#if MI_GUARDED
static mi_decl_restrict void* mi_theap_malloc_guarded_aligned(mi_theap_t* theap, size_t size, size_t alignment, bool zero) mi_attr_noexcept {
#if 0
static mi_decl_noinline mi_decl_restrict void* mi_theap_malloc_guarded_aligned(mi_theap_t* theap, size_t size, size_t alignment, bool zero, mi_page_t** ppage) mi_attr_noexcept {
// use over allocation for guarded blocksl
#if MI_THEAP_INITASNULL
if mi_unlikely(theap==NULL) { theap = _mi_theap_empty_get(); }
@@ -39,7 +39,7 @@ static mi_decl_restrict void* mi_theap_malloc_guarded_aligned(mi_theap_t* theap,
return NULL;
}
const size_t oversize = size + alignment - 1;
void* const base = _mi_theap_malloc_guarded(theap, oversize, zero);
void* const base = _mi_theap_malloc_guarded(theap, oversize, zero, ppage);
if (base==NULL) return NULL;
void* const p = _mi_align_up_ptr(base, alignment);
mi_track_align(base, p, (uint8_t*)p - (uint8_t*)base, size);
@@ -47,32 +47,18 @@ static mi_decl_restrict void* mi_theap_malloc_guarded_aligned(mi_theap_t* theap,
mi_assert_internal(_mi_is_aligned(p, alignment));
return p;
}
static void* mi_theap_malloc_zero_no_guarded(mi_theap_t* theap, size_t size, bool zero, size_t* usable) {
#if MI_THEAP_INITASNULL
if mi_unlikely(theap==NULL) { theap = _mi_theap_empty_get(); }
#endif
const size_t rate = theap->guarded_sample_rate;
// only write if `rate!=0` so we don't write to the constant `_mi_theap_empty`
if (rate != 0) { theap->guarded_sample_rate = 0; }
void* p = _mi_theap_malloc_zero(theap, size, zero, usable);
if (rate != 0) { theap->guarded_sample_rate = rate; }
return p;
}
#else
static void* mi_theap_malloc_zero_no_guarded(mi_theap_t* theap, size_t size, bool zero, size_t* usable) {
return _mi_theap_malloc_zero(theap, size, zero, usable);
}
#endif
// Fallback aligned allocation that over-allocates -- split out for better codegen
static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, size_t* usable) mi_attr_noexcept
static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, mi_page_t** ppage) mi_attr_noexcept
{
mi_assert_internal(size <= (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE));
mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment));
mi_assert_internal(mi_alignment_is_valid(alignment));
void* p;
size_t oversize;
mi_page_t* page;
if mi_unlikely(alignment > MI_PAGE_MAX_OVERALLOC_ALIGN) {
// use OS allocation for large alignments and allocate inside a singleton page (not in an arena)
// This can support alignments >= MI_PAGE_ALIGN by ensuring the object can be aligned
@@ -84,7 +70,7 @@ static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap
}
oversize = (size <= MI_SMALL_SIZE_MAX ? MI_SMALL_SIZE_MAX + 1 /* ensure we use generic malloc path */ : size);
// note: no guarded as alignment > 0
p = _mi_theap_malloc_zero_ex(theap, oversize, zero, alignment, usable); // the page block size should be large enough to align in the single huge page block
p = _mi_theap_malloc_zero(theap, oversize, zero, alignment, &page); // the page block size should be large enough to align in the single huge page block
if (p == NULL) return NULL;
}
else {
@@ -92,9 +78,11 @@ static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap
mi_assert_internal(size <= (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) && alignment <= MI_PAGE_MAX_OVERALLOC_ALIGN);
mi_assert_internal(size < SIZE_MAX - alignment); // `oversize` cannot overflow
oversize = (size < MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : size) + alignment - 1; // adjust for size <= 16; with size 0 and alignment 64k, we would allocate a 64k block and pointing just beyond that.
p = mi_theap_malloc_zero_no_guarded(theap, oversize, zero, usable);
p = _mi_theap_malloc_zero(theap, oversize, zero, 0, &page);
if (p == NULL) return NULL;
}
mi_assert_internal(page == _mi_ptr_page(p));
if (ppage!=NULL) { *ppage = page; }
// .. and align within the allocation
const uintptr_t align_mask = alignment - 1; // for any x, `(x & align_mask) == (x % alignment)`
@@ -107,17 +95,20 @@ static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap
// and we no longer own it. We should be careful to only read constant fields in the page,
// or use safe atomic access as in `mi_page_set_has_interior_pointers`.
// (we can access the page though since the just allocated pointer keeps it alive)
mi_page_t* page = _mi_ptr_page(p);
if (aligned_p != p) {
mi_page_set_has_interior_pointers(page, true);
#if MI_GUARDED
#if MI_GUARDED || MI_PROFILE
// set tag to aligned so mi_usable_size works with guard pages
// note: we could have gotten a profiled or guarded block but that is still ok
// and in such case the MI_BLOCK_TAG_ALIGNED is just not at the start of the real heap block
if (adjust >= sizeof(mi_block_t)) {
mi_block_t* const block = (mi_block_t*)p;
block->next = MI_BLOCK_TAG_ALIGNED;
}
#endif
_mi_padding_shrink(page, (mi_block_t*)p, adjust + size);
mi_block_t* const base = _mi_page_ptr_unalign(page, p);
_mi_padding_shrink(page, base, adjust + size);
}
// todo: expand padding if overallocated ?
@@ -127,22 +118,13 @@ static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap
mi_assert_internal(mi_usable_size(p) == mi_usable_size(aligned_p)+adjust);
#if MI_DEBUG > 1
mi_page_t* const apage = _mi_ptr_page(aligned_p);
void* unalign_p = _mi_page_ptr_unalign(apage, aligned_p);
mi_assert_internal(p == unalign_p);
mi_block_t* unalign_p = _mi_page_ptr_unalign(apage, aligned_p);
mi_assert_internal(p == (void*)unalign_p || mi_block_ptr_is_sampled(unalign_p, aligned_p));
#endif
// now zero the block if needed
//if (alignment > MI_PAGE_MAX_OVERALLOC_ALIGN) {
// // for the tracker, on huge aligned allocations only from the start of the large block is defined
// mi_track_mem_undefined(aligned_p, size);
// if (zero) {
// _mi_memzero_aligned(aligned_p, mi_usable_size(aligned_p));
// }
//}
if (p != aligned_p) {
mi_track_align(p,aligned_p,adjust,mi_usable_size(aligned_p));
#if MI_GUARDED
#if MI_GUARDED || MI_PROFILE
mi_track_mem_defined(p, sizeof(mi_block_t));
#endif
}
@@ -150,85 +132,97 @@ static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_overalloc(mi_theap
}
// Generic primitive aligned allocation -- split out for better codegen
static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_generic(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, size_t* usable) mi_attr_noexcept
static mi_decl_noinline void* mi_theap_malloc_zero_aligned_at_generic(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, mi_page_t** ppage) mi_attr_noexcept
{
mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment));
mi_assert_internal(mi_alignment_is_valid(alignment));
// we don't allocate more than MI_MAX_ALLOC_SIZE (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
if mi_unlikely(size > (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE)) {
_mi_error_message(EOVERFLOW, "aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment);
_mi_error_message(EINVAL, "aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment);
return NULL;
}
// use regular allocation if it is guaranteed to fit the alignment constraints.
// this is important to try as the fast path in `mi_theap_malloc_zero_aligned` only works when there exist
// a page with the right block size, and if we always use the over-alloc fallback that would never happen.
if (offset == 0 && mi_malloc_is_naturally_aligned(size,alignment)) {
void* p = mi_theap_malloc_zero_no_guarded(theap, size, zero, usable);
mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0);
const bool is_aligned_or_null = (((uintptr_t)p) & (alignment-1))==0;
if mi_likely(is_aligned_or_null) {
return p;
}
else {
// this should never happen if the `mi_malloc_is_naturally_aligned` check is correct..
mi_assert(false);
mi_free(p);
}
}
// fall back to over-allocation
return mi_theap_malloc_zero_aligned_at_overalloc(theap,size,alignment,offset,zero,usable);
}
// Primitive aligned allocation
static inline void* mi_theap_malloc_zero_aligned_at(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, size_t* usable) mi_attr_noexcept
{
// note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size.
if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) { // require power-of-two (see <https://en.cppreference.com/w/c/memory/aligned_alloc>)
#if MI_DEBUG > 0
_mi_error_message(EOVERFLOW, "aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu)\n", size, alignment);
#endif
return NULL;
}
#if MI_GUARDED
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL)
#endif
if (offset==0 && alignment < MI_PAGE_MAX_OVERALLOC_ALIGN && mi_theap_malloc_use_guarded(theap,size)) {
return mi_theap_malloc_guarded_aligned(theap, size, alignment, zero);
}
#endif
// try first if there happens to be a small block available with just the right alignment
// since most small power-of-2 blocks (under MI_PAGE_MAX_BLOCK_START_ALIGN2) are already
// naturally aligned this can be often the case.
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL)
#endif
{
if mi_likely(size <= MI_SMALL_SIZE_MAX && alignment <= size) {
const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)`
const size_t padsize = size + MI_PADDING_SIZE;
mi_page_t* page = _mi_theap_get_free_small_page(theap, padsize);
if mi_likely(page->free != NULL) {
const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0;
if mi_likely(is_aligned)
{
if (usable!=NULL) { *usable = mi_page_usable_block_size(page); }
void* p = _mi_page_malloc_zero(theap, page, padsize, zero);
mi_assert_internal(p != NULL);
mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);
mi_track_malloc(p, size, zero);
#if MI_SAMPLE // only try if we would not take a sample
if mi_likely(!mi_theap_should_sample(theap,size))
#endif
{
if (offset == 0 && mi_malloc_is_naturally_aligned(size,alignment))
{
mi_page_t* page = NULL;
void* p = _mi_theap_malloc_zero(theap, size, zero, 0,&page);
if (ppage!=NULL) { *ppage = page; }
const bool is_aligned_or_null = (((uintptr_t)p) & (alignment-1))==0;
if mi_likely(is_aligned_or_null) {
return p;
}
else {
// this should never happen if the `mi_malloc_is_naturally_aligned` check is correct..
// (but it can happen if this allocation just so happens to be sampled but we checked for that)
mi_assert_internal(false); // p==NULL || mi_block_ptr_is_profiled_or_guarded(_mi_page_ptr_unalign(page,p),p));
mi_free(p);
}
}
}
}
// fall back to over-allocation
return mi_theap_malloc_zero_aligned_at_overalloc(theap,size,alignment,offset,zero,ppage);
}
static mi_decl_cold mi_decl_noinline void* mi_error_bad_alignment(size_t size, size_t alignment, size_t offset) {
_mi_error_message(EINVAL, "aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu, offset %zu)\n", size, alignment, offset);
return NULL;
}
// Primitive aligned allocation
static inline void* mi_theap_malloc_zero_aligned_at(mi_theap_t* const theap, const size_t size, const size_t alignment, const size_t offset, const bool zero, mi_page_t** ppage) mi_attr_noexcept
{
// note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size.
if mi_unlikely(!mi_alignment_is_valid(alignment)) { // require power-of-two and multiple of void* (see <https://en.cppreference.com/w/c/memory/aligned_alloc#Notes>)
return mi_error_bad_alignment(size, alignment, offset);
}
// try first if there happens to be a small block available with just the right alignment
// since most small power-of-2 blocks (under MI_PAGE_MAX_BLOCK_START_ALIGN2) are already
// naturally aligned this can be often the case.
if mi_likely(size <= MI_SMALL_SIZE_MAX && alignment <= size)
{
#if MI_THEAP_INITASNULL
if mi_likely(theap!=NULL)
#endif
{
#if MI_SAMPLE // check if we shouldn't take a sample
if mi_likely(!mi_theap_should_sample(theap,size))
#endif
{
const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)`
const size_t padsize = size + MI_PADDING_SIZE;
mi_page_t* page = _mi_theap_get_free_small_page(theap, padsize, false);
if mi_likely(page->free != NULL) {
const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0;
if mi_likely(is_aligned)
{
if (ppage!=NULL) { *ppage = page; }
void* p = _mi_page_malloc_zero(theap, page, padsize, zero); // theap!=NULL
mi_assert_internal(p != NULL);
mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);
mi_track_malloc(p, size, zero);
return p;
}
}
}
}
}
// fallback to generic aligned allocation
return mi_theap_malloc_zero_aligned_at_generic(theap, size, alignment, offset, zero, usable);
return mi_theap_malloc_zero_aligned_at_generic(theap, size, alignment, offset, zero, ppage);
}
@@ -248,7 +242,7 @@ static mi_decl_restrict void* mi_theap_zalloc_aligned_at(mi_theap_t* theap, size
return mi_theap_malloc_zero_aligned_at(theap, size, alignment, offset, true, NULL);
}
static mi_decl_restrict void* mi_theap_zalloc_aligned(mi_theap_t* theap, size_t size, size_t alignment) mi_attr_noexcept {
mi_decl_restrict void* mi_theap_zalloc_aligned(mi_theap_t* theap, size_t size, size_t alignment) mi_attr_noexcept {
return mi_theap_zalloc_aligned_at(theap, size, alignment, 0);
}
@@ -275,8 +269,11 @@ mi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t a
return mi_theap_malloc_aligned(_mi_theap_default(), size, alignment);
}
mi_decl_nodiscard mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept {
return mi_theap_malloc_zero_aligned_at(_mi_theap_default(), size, alignment, 0, false, block_size);
mi_decl_nodiscard mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* pblock_size) mi_attr_noexcept {
mi_page_t* page;
void* p = mi_theap_malloc_zero_aligned_at(_mi_theap_default(), size, alignment, 0, false, &page);
if (p!=NULL && pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
return p;
}
mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
@@ -287,8 +284,11 @@ mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t a
return mi_theap_zalloc_aligned(_mi_theap_default(), size, alignment);
}
mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept {
return mi_theap_malloc_zero_aligned_at(_mi_theap_default(), size, alignment, 0, true, block_size);
mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* pblock_size) mi_attr_noexcept {
mi_page_t* page;
void* p = mi_theap_malloc_zero_aligned_at(_mi_theap_default(), size, alignment, 0, true, &page);
if (p!=NULL && pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
return p;
}
mi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
@@ -330,24 +330,30 @@ mi_decl_nodiscard mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap,
// ------------------------------------------------------
static void* mi_theap_realloc_zero_aligned_at(mi_theap_t* theap, void* p, size_t newsize, size_t alignment, size_t offset, bool zero) mi_attr_noexcept {
mi_assert(alignment > 0);
if (alignment <= sizeof(uintptr_t) && offset==0) return _mi_theap_realloc_zero(theap,p,newsize,zero,NULL,NULL);
mi_assert(mi_alignment_is_valid(alignment));
if mi_unlikely(!mi_alignment_is_valid(alignment)) { // require power-of-two (see <https://en.cppreference.com/w/c/memory/aligned_alloc>)
return mi_error_bad_alignment(newsize,alignment,offset);
}
if (alignment <= sizeof(uintptr_t) && offset==0) return _mi_theap_realloc_zero(theap,p,newsize,zero);
if (p == NULL) return mi_theap_malloc_zero_aligned_at(theap,newsize,alignment,offset,zero,NULL);
size_t size = mi_usable_size(p);
if (newsize <= size && newsize >= (size - (size / 2))
&& (((uintptr_t)p + offset) % alignment) == 0) {
return p; // reallocation still fits, is aligned and not more than 25% waste
const size_t size = mi_usable_size(p);
if (newsize <= size && newsize >= (size - (size / 2)) && (((uintptr_t)p + offset) & (alignment-1)) == 0) {
return p; // reallocation still fits, is aligned and not more than 50% waste
}
else {
// note: we don't zero allocate upfront so we only zero initialize the expanded part
void* newp = mi_theap_malloc_aligned_at(theap,newsize,alignment,offset);
// note: we don't zero allocate upfront so we only zero initialize the expanded part (at the cost of calling mi_usable_size)
mi_page_t* newpage;
void* const newp = mi_theap_malloc_zero_aligned_at(theap,newsize,alignment,offset,false/*zero?*/,&newpage);
if (newp != NULL) {
if (zero && newsize > size) {
const size_t copy_size = (newsize > size ? size : newsize);
const size_t zero_start = (copy_size >= sizeof(intptr_t) ? copy_size - sizeof(intptr_t) : 0); // also set last word in the previous allocation to zero to ensure any padding is zero-initialized
const size_t usable = _mi_page_usable_size(newpage,newp);
mi_assert_internal(usable >= newsize); // use usable for zero'ing, issue #763
if (zero && usable > zero_start) {
// also set last word in the previous allocation to zero to ensure any padding is zero-initialized
size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0);
_mi_memzero((uint8_t*)newp + start, newsize - start);
_mi_memzero((uint8_t*)newp + zero_start, usable - zero_start);
}
_mi_memcpy_aligned(newp, p, (newsize > size ? size : newsize));
_mi_memcpy(newp, p, copy_size); // cannot be aligned due to abitrary offset... (todo: require offset to be a multiple of sizeof(void*)?)
mi_free(p); // only free if successful
}
return newp;
@@ -356,7 +362,7 @@ static void* mi_theap_realloc_zero_aligned_at(mi_theap_t* theap, void* p, size_t
static void* mi_theap_realloc_zero_aligned(mi_theap_t* theap, void* p, size_t newsize, size_t alignment, bool zero) mi_attr_noexcept {
mi_assert(alignment > 0);
if (alignment <= sizeof(uintptr_t)) return _mi_theap_realloc_zero(theap,p,newsize,zero,NULL,NULL);
if (alignment <= sizeof(uintptr_t)) return _mi_theap_realloc_zero(theap,p,newsize,zero);
return mi_theap_realloc_zero_aligned_at(theap,p,newsize,alignment,0,zero);
}
+17 -6
View File
@@ -127,7 +127,7 @@ typedef void* mi_nothrow_t;
#elif defined(_MSC_VER)
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRT_HYBRIDPATCHABLE
void* __cdecl _expand(_Pre_notnull_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Size) {
return mi_expand(_Block, _Size);
return mi__expand(_Block, _Size);
}
_Check_return_ _ACRTIMP
size_t __cdecl _msize_base(_Pre_notnull_ void* _Block) _CRT_NOEXCEPT {
@@ -293,7 +293,7 @@ typedef void* mi_nothrow_t;
void _ZdlPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete(void*, std::align_val_t, std::nothrow_t const&)
void _ZdaPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete[](void*, std::align_val_t, std::nothrow_t const&)
#if (MI_INTPTR_SIZE==8) || (MI_INTPTR_SIZE==4 && defined(__EMSCRIPTEN__)) // pr #1257
#if (MI_SIZE_SIZE==8) || (MI_SIZE_SIZE==4 && defined(__EMSCRIPTEN__)) // pr #1257
void* _Znwm(size_t n) MI_FORWARD1(mi_new,n) // new 64-bit
void* _Znam(size_t n) MI_FORWARD1(mi_new,n) // new[] 64-bit
void* _ZnwmRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }
@@ -302,7 +302,7 @@ typedef void* mi_nothrow_t;
void* _ZnamSt11align_val_t(size_t n, size_t al) MI_FORWARD2(mi_new_aligned, n, al)
void* _ZnwmSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }
void* _ZnamSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_aligned_nothrow(n,al); }
#elif (MI_INTPTR_SIZE==4)
#elif (MI_SIZE_SIZE==4)
void* _Znwj(size_t n) MI_FORWARD1(mi_new,n) // new 64-bit
void* _Znaj(size_t n) MI_FORWARD1(mi_new,n) // new[] 64-bit
void* _ZnwjRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }
@@ -324,14 +324,25 @@ typedef void* mi_nothrow_t;
extern "C" {
#endif
// defined here instead alloc-posix so we can alias it
mi_decl_nodiscard size_t mi_malloc_size(const void* p) mi_attr_noexcept {
if (!mi_is_in_heap_region(p)) return 0;
return mi_usable_size(p);
}
mi_decl_nodiscard size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept {
if (!mi_is_in_heap_region(p)) return 0;
return mi_usable_size(p);
}
#ifndef MI_OSX_IS_INTERPOSED
// Forward Posix/Unix calls as well
void* reallocf(void* p, size_t newsize) MI_FORWARD2(mi_reallocf,p,newsize)
size_t malloc_size(const void* p) MI_FORWARD1(mi_usable_size,p)
size_t malloc_size(const void* p) MI_FORWARD1(mi_malloc_size,p)
#if !defined(__ANDROID__) && !defined(__FreeBSD__) && !defined(__DragonFly__)
size_t malloc_usable_size(void *p) MI_FORWARD1(mi_usable_size,p)
size_t malloc_usable_size(void *p) MI_FORWARD1(mi_malloc_usable_size,p)
#else
size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_usable_size,p)
size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_malloc_usable_size,p)
#endif
// No forwarding here due to aliasing/name mangling issues
+4 -22
View File
@@ -31,34 +31,16 @@ terms of the MIT license. A copy of the license can be found in the file
#define ENOMEM 12
#endif
mi_decl_nodiscard size_t mi_malloc_size(const void* p) mi_attr_noexcept {
// if (!mi_is_in_heap_region(p)) return 0;
return mi_usable_size(p);
}
mi_decl_nodiscard size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept {
// if (!mi_is_in_heap_region(p)) return 0;
return mi_usable_size(p);
}
mi_decl_nodiscard size_t mi_malloc_good_size(size_t size) mi_attr_noexcept {
return mi_good_size(size);
}
void mi_cfree(void* p) mi_attr_noexcept {
if (mi_is_in_heap_region(p)) {
mi_free(p);
}
}
int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept {
int mi_posix_memalign(void** p, size_t alignment, size_t size) { // mi_attr_noexcept (issue #794)
// Note: The spec dictates we should not modify `*p` on an error. (issue#27)
// <http://man7.org/linux/man-pages/man3/posix_memalign.3.html>
if (p == NULL) return EINVAL;
if ((alignment % sizeof(void*)) != 0) return EINVAL; // natural alignment
// it is also required that alignment is a power of 2 and > 0; this is checked in `mi_malloc_aligned`
if (alignment==0 || !_mi_is_power_of_two(alignment)) return EINVAL; // not a power of 2
// it is required that alignment is a power of 2 and a multiple of sizeof(void*)
if (alignment<sizeof(void*) || !_mi_is_power_of_two(alignment)) return EINVAL; // not a power of 2
void* q = mi_malloc_aligned(size, alignment);
if (q==NULL && size != 0) return ENOMEM;
mi_assert_internal(_mi_is_aligned(q,alignment));
@@ -122,7 +104,7 @@ mi_decl_nodiscard int mi_reallocarr( void* ptrp, size_t count, size_t size ) mi_
}
void** op = (void**)ptrp;
if (total == 0) {
free(*op);
mi_free(*op);
*op = NULL;
return 0;
}
File diff suppressed because it is too large. Load diff
-179
View File
@@ -1,179 +0,0 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2019-2024, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
/* ----------------------------------------------------------------------------
We have a special "mini" allocator just for allocation of meta-data like
the theap (`mi_theap_t`) or thread-local data (`mi_tld_t`).
We reuse the bitmap of the arena's for allocation of 64b blocks inside
an arena slice (64KiB).
We always ensure that meta data is zero'd (we zero on `free`)
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "bitmap.h"
/* -----------------------------------------------------------
Meta data allocation
----------------------------------------------------------- */
#define MI_META_PAGE_SIZE MI_ARENA_SLICE_SIZE
#define MI_META_PAGE_ALIGN MI_ARENA_SLICE_ALIGN
// large enough such that META_MAX_SIZE > 4k (even on 32-bit)
#define MI_META_BLOCK_SIZE (1 << (16 - MI_BCHUNK_BITS_SHIFT)) // 128 on 64-bit
#define MI_META_BLOCK_ALIGN MI_META_BLOCK_SIZE
#define MI_META_BLOCKS_PER_PAGE (MI_META_PAGE_SIZE / MI_META_BLOCK_SIZE) // 512
#define MI_META_MAX_SIZE (MI_BCHUNK_SIZE * MI_META_BLOCK_SIZE)
#if MI_META_MAX_SIZE <= 4096
#error "max meta object size should be at least 4KiB"
#endif
typedef struct mi_meta_page_s {
_Atomic(struct mi_meta_page_s*) next; // a linked list of meta-data pages (never released)
mi_memid_t memid; // provenance of the meta-page memory itself
mi_bbitmap_t blocks_free; // a small bitmap with 1 bit per block.
} mi_meta_page_t;
static mi_decl_cache_align _Atomic(mi_meta_page_t*) mi_meta_pages = MI_ATOMIC_VAR_INIT(NULL);
#if MI_DEBUG > 1
static mi_meta_page_t* mi_meta_page_of_ptr(void* p, size_t* block_idx) {
mi_meta_page_t* mpage = (mi_meta_page_t*)((uint8_t*)_mi_align_down_ptr(p,MI_META_PAGE_ALIGN) + _mi_os_secure_guard_page_size());
if (block_idx != NULL) {
*block_idx = ((uint8_t*)p - (uint8_t*)mpage) / MI_META_BLOCK_SIZE;
}
return mpage;
}
#endif
static mi_meta_page_t* mi_meta_page_next( mi_meta_page_t* mpage ) {
return mi_atomic_load_ptr_acquire(mi_meta_page_t, &mpage->next);
}
static void* mi_meta_block_start( mi_meta_page_t* mpage, size_t block_idx ) {
mi_assert_internal(_mi_is_aligned((uint8_t*)mpage - _mi_os_secure_guard_page_size(), MI_META_PAGE_ALIGN));
mi_assert_internal(block_idx < MI_META_BLOCKS_PER_PAGE);
void* p = ((uint8_t*)mpage - _mi_os_secure_guard_page_size() + (block_idx * MI_META_BLOCK_SIZE));
mi_assert_internal(mpage == mi_meta_page_of_ptr(p,NULL));
return p;
}
// allocate a fresh meta page and add it to the global list.
static mi_meta_page_t* mi_meta_page_zalloc(void) {
// allocate a fresh arena slice
// note: careful with _mi_subproc as it may recurse into mi_tld and meta_page_zalloc again.. (same with _mi_os_numa_node()...)
mi_memid_t memid;
uint8_t* base = (uint8_t*)_mi_arenas_alloc_aligned(mi_heap_main(), MI_META_PAGE_SIZE, MI_META_PAGE_ALIGN, 0,
true /* commit*/, (MI_SECURE==0) /* allow large? */,
NULL /* req arena */, 0 /* thread_seq */, -1 /* numa node */, &memid);
if (base == NULL) return NULL;
mi_assert_internal(_mi_is_aligned(base,MI_META_PAGE_ALIGN));
if (!memid.initially_zero) {
_mi_memzero_aligned(base, MI_ARENA_SLICE_SIZE);
}
// guard pages
#if MI_SECURE >= 1
_mi_os_secure_guard_page_set_at(base, memid);
_mi_os_secure_guard_page_set_before(base + MI_META_PAGE_SIZE, memid);
#endif
// initialize the page and free block bitmap
mi_meta_page_t* mpage = (mi_meta_page_t*)(base + _mi_os_secure_guard_page_size());
mpage->memid = memid;
mi_bbitmap_init(&mpage->blocks_free, MI_META_BLOCKS_PER_PAGE, true /* already_zero */);
const size_t mpage_size = offsetof(mi_meta_page_t,blocks_free) + mi_bbitmap_size(MI_META_BLOCKS_PER_PAGE, NULL);
const size_t info_blocks = _mi_divide_up(mpage_size,MI_META_BLOCK_SIZE);
const size_t guard_blocks = _mi_divide_up(_mi_os_secure_guard_page_size(), MI_META_BLOCK_SIZE);
mi_assert_internal(info_blocks + 2*guard_blocks < MI_META_BLOCKS_PER_PAGE);
mi_bbitmap_unsafe_setN(&mpage->blocks_free, info_blocks + guard_blocks, MI_META_BLOCKS_PER_PAGE - info_blocks - 2*guard_blocks);
// push atomically in front of the meta page list
// (note: there is no ABA issue since we never free meta-pages)
mi_meta_page_t* old = mi_atomic_load_ptr_acquire(mi_meta_page_t,&mi_meta_pages);
do {
mi_atomic_store_ptr_release(mi_meta_page_t, &mpage->next, old);
} while(!mi_atomic_cas_ptr_weak_acq_rel(mi_meta_page_t,&mi_meta_pages,&old,mpage));
return mpage;
}
// allocate meta-data
mi_decl_noinline void* _mi_meta_zalloc( size_t size, mi_memid_t* pmemid )
{
mi_assert_internal(pmemid != NULL);
size = _mi_align_up(size,MI_META_BLOCK_SIZE);
if (size == 0 || size > MI_META_MAX_SIZE) return NULL;
const size_t block_count = _mi_divide_up(size,MI_META_BLOCK_SIZE);
mi_assert_internal(block_count > 0 && block_count < MI_BCHUNK_BITS);
mi_meta_page_t* mpage0 = mi_atomic_load_ptr_acquire(mi_meta_page_t,&mi_meta_pages);
mi_meta_page_t* mpage = mpage0;
while (mpage != NULL) {
size_t block_idx;
if (mi_bbitmap_try_find_and_clearN(&mpage->blocks_free, 0, block_count, &block_idx)) {
// found and claimed `block_count` blocks
*pmemid = _mi_memid_create_meta(mpage, block_idx, block_count);
return mi_meta_block_start(mpage,block_idx);
}
else {
mpage = mi_meta_page_next(mpage);
}
}
// failed to find space in existing pages
if (mi_atomic_load_ptr_acquire(mi_meta_page_t,&mi_meta_pages) != mpage0) {
// the page list was updated by another thread in the meantime, retry
return _mi_meta_zalloc(size,pmemid);
}
// otherwise, allocate a fresh metapage and try once more
mpage = mi_meta_page_zalloc();
if (mpage != NULL) {
size_t block_idx;
if (mi_bbitmap_try_find_and_clearN(&mpage->blocks_free, 0, block_count, &block_idx)) {
// found and claimed `block_count` blocks
*pmemid = _mi_memid_create_meta(mpage, block_idx, block_count);
return mi_meta_block_start(mpage,block_idx);
}
}
// if all this failed, allocate from the OS
return _mi_os_alloc(size, pmemid);
}
// free meta-data
mi_decl_noinline void _mi_meta_free(void* p, size_t size, mi_memid_t memid) {
if (p==NULL) return;
if (memid.memkind == MI_MEM_META) {
mi_assert_internal(_mi_divide_up(size, MI_META_BLOCK_SIZE) == memid.mem.meta.block_count);
const size_t block_count = memid.mem.meta.block_count;
const size_t block_idx = memid.mem.meta.block_index;
mi_meta_page_t* mpage = (mi_meta_page_t*)memid.mem.meta.meta_page;
mi_assert_internal(mi_meta_page_of_ptr(p,NULL) == mpage);
mi_assert_internal(block_idx + block_count <= MI_META_BLOCKS_PER_PAGE);
mi_assert_internal(mi_bbitmap_is_clearN(&mpage->blocks_free, block_idx, block_count));
// we zero on free (and on the initial page allocation) so we don't need a "dirty" map
_mi_memzero_aligned(mi_meta_block_start(mpage, block_idx), block_count*MI_META_BLOCK_SIZE);
mi_bbitmap_setN(&mpage->blocks_free, block_idx, block_count);
}
else {
_mi_arenas_free(p,size,memid);
}
}
// used for debug output
bool _mi_meta_is_meta_page(void* p)
{
mi_meta_page_t* mpage0 = mi_atomic_load_ptr_acquire(mi_meta_page_t, &mi_meta_pages);
mi_meta_page_t* mpage = mpage0;
while (mpage != NULL) {
if ((void*)mpage == p) return true;
mpage = mi_meta_page_next(mpage);
}
return false;
}
File diff suppressed because it is too large. Load diff
+200 -147
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2019-2024 Microsoft Research, Daan Leijen
Copyright (c) 2019-2026 Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -107,9 +107,9 @@ static inline bool mi_bfield_atomic_clear(_Atomic(mi_bfield_t)*b, size_t idx, bo
}
// Clear a bit but only when/once it is set. This is used by concurrent free's while
// the page is abandoned and mapped. This can incure a busy wait :-( but it should
// happen almost never (and is accounted for in the stats)
static inline void mi_bfield_atomic_clear_once_set(_Atomic(mi_bfield_t)*b, size_t idx) {
// the page is abandoned and mapped. This can incur a busy wait :-( but it should
// be quite rare (and is accounted for in the stats)
static inline void mi_bfield_atomic_clear_once_set(mi_subproc_t* subproc, _Atomic(mi_bfield_t)*b, size_t idx) {
mi_assert_internal(idx < MI_BFIELD_BITS);
const mi_bfield_t mask = mi_bfield_mask(1, idx);;
mi_bfield_t old = mi_atomic_load_relaxed(b);
@@ -117,7 +117,7 @@ static inline void mi_bfield_atomic_clear_once_set(_Atomic(mi_bfield_t)*b, size_
if mi_unlikely((old&mask) == 0) {
old = mi_atomic_load_acquire(b);
if ((old&mask)==0) {
mi_subproc_stat_counter_increase(_mi_subproc(), pages_unabandon_busy_wait, 1);
mi_subproc_stat_counter_increase(subproc, pages_unabandon_busy_wait, 1);
}
while ((old&mask)==0) { // busy wait
_mi_prim_thread_yield();
@@ -133,8 +133,7 @@ static inline void mi_bfield_atomic_clear_once_set(_Atomic(mi_bfield_t)*b, size_
// statistics correctly).
static inline bool mi_bfield_atomic_set_mask(_Atomic(mi_bfield_t)*b, mi_bfield_t mask, size_t* already_set) {
mi_assert_internal(mask != 0);
mi_bfield_t old = mi_atomic_load_relaxed(b);
while (!mi_atomic_cas_weak_acq_rel(b, &old, old|mask)) {}; // try to atomically set the mask bits until success
const mi_bfield_t old = mi_atomic_or_acq_rel(b,mask);
if (already_set!=NULL) { *already_set = mi_bfield_popcount(old&mask); }
return ((old&mask) == 0);
}
@@ -143,8 +142,7 @@ static inline bool mi_bfield_atomic_set_mask(_Atomic(mi_bfield_t)*b, mi_bfield_t
// `all_clear` is set to `true` if the new bfield became zero.
static inline bool mi_bfield_atomic_clear_mask(_Atomic(mi_bfield_t)*b, mi_bfield_t mask, bool* all_clear) {
mi_assert_internal(mask != 0);
mi_bfield_t old = mi_atomic_load_relaxed(b);
while (!mi_atomic_cas_weak_acq_rel(b, &old, old&~mask)) {}; // try to atomically clear the mask bits until success
const mi_bfield_t old = mi_atomic_and_acq_rel(b,~mask);
if (all_clear != NULL) { *all_clear = ((old&~mask)==0); }
return ((old&mask) == mask);
}
@@ -155,65 +153,79 @@ static inline bool mi_bfield_atomic_setX(_Atomic(mi_bfield_t)*b, size_t* already
return (old==0);
}
// static inline bool mi_bfield_atomic_clearX(_Atomic(mi_bfield_t)*b, bool* all_clear) {
// const mi_bfield_t old = mi_atomic_exchange_release(b, mi_bfield_zero());
// if (all_clear!=NULL) { *all_clear = true; }
// return (~old==0);
// }
// ------- mi_bfield_atomic_try_clear ---------------------------------------
// Tries to clear a mask atomically, and returns true if the mask bits atomically transitioned from mask to 0
// and false otherwise (leaving the bit field as is).
// and false otherwise (leaving the bit field as is). Returns the acquired value back in `expect` (regardless of success).
// `all_clear` is set to `true` if the new bfield became zero.
static inline bool mi_bfield_atomic_try_clear_mask_of(_Atomic(mi_bfield_t)*b, mi_bfield_t mask, mi_bfield_t expect, bool* all_clear) {
// `did_temp_clear_bits` is set to `true` some bits were temporarily cleared.
static inline bool mi_bfield_atomic_try_clear_mask_optimistic(_Atomic(mi_bfield_t)*b, mi_bfield_t mask, mi_bfield_t* previous, bool* did_temp_clear_bits) {
mi_assert_internal(mask != 0);
// note: we could also use a strong cas but generally an optimistic atomic and/or is more efficient (at least on arm64)
// try to atomically clear the mask bits
do {
if ((expect & mask) != mask) { // are all bits still set?
if (all_clear != NULL) { *all_clear = (expect == 0); }
return false;
mi_bfield_t old = mi_atomic_and_acq_rel(b,~mask);
if (previous!=NULL) { *previous = old; }
if mi_likely((old&mask)==mask) {
// all bits in the mask transitioned from 1 to 0 atomically
return true;
}
else {
// failed to transition all
if ((old&mask)!=0) {
// restore accidentally cleared ones
mi_atomic_or_acq_rel(b, old&mask);
if (did_temp_clear_bits!=NULL) { *did_temp_clear_bits = true; }
}
} while (!mi_atomic_cas_weak_acq_rel(b, &expect, expect & ~mask));
if (all_clear != NULL) { *all_clear = ((expect & ~mask) == 0); }
return true;
return false;
}
}
static inline bool mi_bfield_atomic_try_clear_mask(_Atomic(mi_bfield_t)* b, mi_bfield_t mask, bool* all_clear) {
mi_assert_internal(mask != 0);
const mi_bfield_t expect = mi_atomic_load_relaxed(b);
return mi_bfield_atomic_try_clear_mask_of(b, mask, expect, all_clear);
}
// Tries to clear a bit atomically. Returns `true` if the bit transitioned from 1 to 0
// and `false` otherwise leaving the bfield `b` as-is.
// Tries to clear a bit atomically. For performance, it assumes there is a good chance of success.
// Returns `true` if the bit transitioned from 1 to 0
// and `false` otherwise (leaving the bfield `b` as-is).
// `all_clear` is set to true if the new bfield became zero (and false otherwise)
mi_decl_maybe_unused static inline bool mi_bfield_atomic_try_clear(_Atomic(mi_bfield_t)* b, size_t idx, bool* all_clear) {
mi_decl_maybe_unused static inline bool mi_bfield_atomic_try_clear_optimistic(_Atomic(mi_bfield_t)* b, size_t idx, mi_bfield_t* previous) {
mi_assert_internal(idx < MI_BFIELD_BITS);
const mi_bfield_t mask = mi_bfield_one()<<idx;
return mi_bfield_atomic_try_clear_mask(b, mask, all_clear);
const mi_bfield_t mask = ((mi_bfield_t)1<<idx);
return mi_bfield_atomic_try_clear_mask_optimistic(b, mask, previous, NULL); // single bit never clears temporarily
}
// Tries to clear a byte atomically, and returns true if the byte atomically transitioned from 0xFF to 0
// Tries to clear a byte atomically. For performance, it assumes there is a good chance of success.
// Returns true if the byte atomically transitioned from 0xFF to 0
// `all_clear` is set to true if the new bfield became zero (and false otherwise)
mi_decl_maybe_unused static inline bool mi_bfield_atomic_try_clear8(_Atomic(mi_bfield_t)*b, size_t idx, bool* all_clear) {
mi_decl_maybe_unused static inline bool mi_bfield_atomic_try_clear8_optimistic(_Atomic(mi_bfield_t)*b, size_t idx, mi_bfield_t* previous, bool* did_temp_clear_bits) {
mi_assert_internal(idx < MI_BFIELD_BITS);
mi_assert_internal((idx%8)==0);
const mi_bfield_t mask = ((mi_bfield_t)0xFF)<<idx;
return mi_bfield_atomic_try_clear_mask(b, mask, all_clear);
return mi_bfield_atomic_try_clear_mask_optimistic(b, mask, previous, did_temp_clear_bits);
}
// Try to clear a mask of bits atomically. For performance, it assumes these is a good chance of success.
// Returns true all bits transitioned from all 1's to 0's.
// and false otherwise leaving the bit field as-is. `all_clear` is set to true if the new bfield became zero.
static inline bool mi_bfield_atomic_try_clear_mask(_Atomic(mi_bfield_t)* b, mi_bfield_t mask, bool* all_clear, bool* did_temp_clear_bits) {
mi_assert_internal(mask != 0);
mi_bfield_t old = mi_atomic_load_relaxed(b); // check first if there is a good chance the mask bits are all set
bool cleared = false;
if ((old&mask)==mask) {
cleared = mi_bfield_atomic_try_clear_mask_optimistic(b, mask, &old, did_temp_clear_bits);
}
if (all_clear != NULL) { *all_clear = ((cleared ? old&~mask : old) == 0); }
return cleared;
}
// Try to clear a full field of bits atomically, and return true all bits transitioned from all 1's to 0's.
// and false otherwise leaving the bit field as-is.
// `all_clear` is set to true if the new bfield became zero (which is always the case if successful).
static inline bool mi_bfield_atomic_try_clearX(_Atomic(mi_bfield_t)*b, bool* all_clear) {
mi_bfield_t old = mi_bfield_all_set();
if (mi_atomic_cas_strong_acq_rel(b, &old, mi_bfield_zero())) {
if (all_clear != NULL) { *all_clear = true; }
return true;
mi_bfield_t old = mi_atomic_load_relaxed(b); // check first if there is a good chance the bits are all set
bool cleared = false;
if (old==mi_bfield_all_set()) {
cleared = mi_atomic_cas_strong_acq_rel(b,&old,mi_bfield_zero());
}
else return false;
if (all_clear != NULL) { *all_clear = (cleared ? true : (old==0)); }
return cleared;
}
@@ -331,7 +343,7 @@ mi_decl_noinline static bool mi_bchunk_xsetNC(mi_xset_t set, mi_bchunk_t* chunk,
bool all_clear = false;
const bool transition = (set ? mi_bfield_atomic_set_mask(&chunk->bfields[field], mask, &already_set)
: mi_bfield_atomic_clear_mask(&chunk->bfields[field], mask, &all_clear));
mi_assert_internal((transition && already_set == 0) || (!transition && already_set > 0));
mi_assert_internal(!set || ((transition && already_set == 0) || (!transition && already_set > 0)));
all_transition = all_transition && transition;
total_already_set += already_set;
maybe_all_clear = maybe_all_clear && all_clear;
@@ -369,7 +381,7 @@ static inline bool mi_bchunk_clearN(mi_bchunk_t* chunk, size_t cidx, size_t n, b
if (n==1) return mi_bchunk_clear(chunk, cidx, maybe_all_clear);
// if (n==8) return mi_bchunk_clear8(chunk, cidx, maybe_all_clear);
// if (n==MI_BFIELD_BITS) return mi_bchunk_clearX(chunk, cidx, maybe_all_clear);
// TODO: implement mi_bchunk_xsetNX instead of setNX
// todo: implement mi_bchunk_xsetNX instead of setNX
return mi_bchunk_xsetNC(MI_BIT_CLEAR, chunk, cidx, n, NULL, maybe_all_clear);
}
@@ -445,14 +457,14 @@ static inline bool mi_bchunk_is_xsetN(mi_xset_t set, const mi_bchunk_t* chunk, s
// ------- mi_bchunk_try_clear ---------------------------------------
// Clear `0 < n <= MI_BITFIELD_BITS`. Can cross over a bfield boundary.
static inline bool mi_bchunk_try_clearNX(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* pmaybe_all_clear) {
static inline bool mi_bchunk_try_clearNX(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* pmaybe_all_clear, bool* did_temp_clear_bits) {
mi_assert_internal(cidx < MI_BCHUNK_BITS);
mi_assert_internal(n <= MI_BFIELD_BITS);
const size_t i = cidx / MI_BFIELD_BITS;
const size_t idx = cidx % MI_BFIELD_BITS;
if mi_likely(idx + n <= MI_BFIELD_BITS) {
// within one field
return mi_bfield_atomic_try_clear_mask(&chunk->bfields[i], mi_bfield_mask(n, idx), pmaybe_all_clear);
return mi_bfield_atomic_try_clear_mask(&chunk->bfields[i], mi_bfield_mask(n, idx), pmaybe_all_clear, did_temp_clear_bits);
}
else {
// spanning two fields (todo: use double-word atomic ops?)
@@ -460,14 +472,15 @@ static inline bool mi_bchunk_try_clearNX(mi_bchunk_t* chunk, size_t cidx, size_t
mi_assert_internal(m < n);
mi_assert_internal(i < MI_BCHUNK_FIELDS - 1);
bool field1_is_clear;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[i], mi_bfield_mask(m, idx), &field1_is_clear)) return false;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[i], mi_bfield_mask(m, idx), &field1_is_clear, did_temp_clear_bits)) return false;
// try the second field as well
mi_assert_internal(n - m > 0);
mi_assert_internal(n - m < MI_BFIELD_BITS);
bool field2_is_clear;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[i+1], mi_bfield_mask(n - m, 0), &field2_is_clear)) {
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[i+1], mi_bfield_mask(n - m, 0), &field2_is_clear, did_temp_clear_bits)) {
// we failed to clear the second field, restore the first one
mi_bfield_atomic_set_mask(&chunk->bfields[i], mi_bfield_mask(m, idx), NULL);
if (did_temp_clear_bits != NULL) { *did_temp_clear_bits = true; }
return false;
}
if (pmaybe_all_clear != NULL) { *pmaybe_all_clear = field1_is_clear && field2_is_clear; }
@@ -488,7 +501,7 @@ static inline bool mi_bchunk_try_clearNX(mi_bchunk_t* chunk, size_t cidx, size_t
// and false otherwise leaving all bit fields as is.
// Note: this is the complex one as we need to unwind partial atomic operations if we fail halfway..
// `maybe_all_clear` is set to `true` if all the bfields involved become zero.
mi_decl_noinline static bool mi_bchunk_try_clearNC(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* pmaybe_all_clear) {
mi_decl_noinline static bool mi_bchunk_try_clearNC(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* pmaybe_all_clear, bool* did_temp_clear_bits) {
mi_assert_internal(cidx + n <= MI_BCHUNK_BITS);
mi_assert_internal(n>0);
if (pmaybe_all_clear != NULL) { *pmaybe_all_clear = true; }
@@ -504,7 +517,7 @@ mi_decl_noinline static bool mi_bchunk_try_clearNC(mi_bchunk_t* chunk, size_t ci
mi_assert_internal(start_field < MI_BCHUNK_FIELDS);
const mi_bfield_t mask_start = mi_bfield_mask(m, start_idx);
bool maybe_all_clear;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[field], mask_start, &maybe_all_clear)) return false;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[field], mask_start, &maybe_all_clear, did_temp_clear_bits)) return false;
// done?
mi_assert_internal(m <= n);
@@ -528,7 +541,7 @@ mi_decl_noinline static bool mi_bchunk_try_clearNC(mi_bchunk_t* chunk, size_t ci
mi_assert_internal(field < MI_BCHUNK_FIELDS);
const mi_bfield_t mask_end = mi_bfield_mask(n, 0);
bool field_is_clear;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[field], mask_end, &field_is_clear)) goto restore;
if (!mi_bfield_atomic_try_clear_mask(&chunk->bfields[field], mask_end, &field_is_clear, did_temp_clear_bits)) goto restore;
maybe_all_clear = maybe_all_clear && field_is_clear;
}
@@ -538,6 +551,7 @@ mi_decl_noinline static bool mi_bchunk_try_clearNC(mi_bchunk_t* chunk, size_t ci
restore:
// `field` is the index of the field that failed to set atomically; we need to restore all previous fields
mi_assert_internal(field > start_field);
if (did_temp_clear_bits != NULL) { *did_temp_clear_bits = true; }
while( field > start_field) {
field--;
if (field == start_field) {
@@ -551,11 +565,11 @@ restore:
}
static inline bool mi_bchunk_try_clearN(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* maybe_all_clear) {
static inline bool mi_bchunk_try_clearN(mi_bchunk_t* chunk, size_t cidx, size_t n, bool* maybe_all_clear, bool* did_temp_clear_bits) {
mi_assert_internal(n>0);
// if (n==MI_BFIELD_BITS) return mi_bchunk_try_clearX(chunk, cidx, maybe_all_clear);
if (n<=MI_BFIELD_BITS) return mi_bchunk_try_clearNX(chunk, cidx, n, maybe_all_clear);
return mi_bchunk_try_clearNC(chunk, cidx, n, maybe_all_clear);
if (n<=MI_BFIELD_BITS) return mi_bchunk_try_clearNX(chunk, cidx, n, maybe_all_clear, did_temp_clear_bits);
return mi_bchunk_try_clearNC(chunk, cidx, n, maybe_all_clear, did_temp_clear_bits);
}
@@ -576,19 +590,24 @@ mi_decl_maybe_unused static inline bool mi_mm256_is_zero( __m256i vec) {
}
#endif
static inline bool mi_bchunk_try_find_and_clear_at(mi_bchunk_t* chunk, size_t chunk_idx, size_t* pidx) {
mi_assert_internal(chunk_idx < MI_BCHUNK_FIELDS);
// note: this must be acquire (and not relaxed), or otherwise the AVX code below can loop forever
// as the compiler won't reload the registers vec1 and vec2 from memory again.
const mi_bfield_t b = mi_atomic_load_acquire(&chunk->bfields[chunk_idx]);
size_t idx;
if (mi_bfield_find_least_bit(b, &idx)) { // find the least bit
if mi_likely(mi_bfield_atomic_try_clear_mask_of(&chunk->bfields[chunk_idx], mi_bfield_mask(1,idx), b, NULL)) { // clear it atomically
*pidx = (chunk_idx*MI_BFIELD_BITS) + idx;
_Atomic(mi_bfield_t)* const bfield = &chunk->bfields[chunk_idx];
mi_bfield_t b = mi_atomic_load_relaxed(bfield);
if (b==0) return false;
int tries = 0;
do {
const mi_bfield_t mask = (b & (~b+1)); // == (b & -b) but avoids a compiler warning -- clear all bits except the least-significant one
b = mi_atomic_and_acq_rel(bfield,~mask); // clear the bit and set `b` to the previous value
if mi_likely((b&mask)==mask) { // if we transitioned from 1 to 0, we actually cleared it
size_t bitidx = 0;
mi_bfield_find_least_bit(mask,&bitidx);
*pidx = (chunk_idx*MI_BFIELD_BITS) + bitidx;
mi_assert_internal(*pidx < MI_BCHUNK_BITS);
return true;
}
}
}
} while (b!=0 && ++tries <= 4); // limit tries to reduce possible contention
return false;
}
@@ -686,28 +705,36 @@ static inline bool mi_bchunk_try_find_and_clear(mi_bchunk_t* chunk, size_t* pidx
return false;
}
static inline bool mi_bchunk_try_find_and_clear_1(mi_bchunk_t* chunk, size_t n, size_t* pidx) {
mi_assert_internal(n==1); MI_UNUSED(n);
static inline bool mi_bchunk_try_find_and_clear_1(mi_bchunk_t* chunk, size_t n, size_t* pidx, bool* did_temp_clear_bits) {
mi_assert_internal(n==1); MI_UNUSED(n); MI_UNUSED(did_temp_clear_bits);
return mi_bchunk_try_find_and_clear(chunk, pidx);
}
mi_decl_maybe_unused static inline bool mi_bchunk_try_find_and_clear8_at(mi_bchunk_t* chunk, size_t chunk_idx, size_t* pidx) {
const mi_bfield_t b = mi_atomic_load_relaxed(&chunk->bfields[chunk_idx]);
// has_set8 has low bit in each byte set if the byte in x == 0xFF
const mi_bfield_t has_set8 =
((~b - MI_BFIELD_LO_BIT8) & // high bit set if byte in x is 0xFF or < 0x7F
(b & MI_BFIELD_HI_BIT8)) // high bit set if byte in x is >= 0x80
>> 7; // shift high bit to low bit
size_t idx;
if (mi_bfield_find_least_bit(has_set8, &idx)) { // find least 1-bit
mi_assert_internal(idx <= (MI_BFIELD_BITS - 8));
mi_assert_internal((idx%8)==0);
if mi_likely(mi_bfield_atomic_try_clear_mask_of(&chunk->bfields[chunk_idx], (mi_bfield_t)0xFF << idx, b, NULL)) { // unset the byte atomically
*pidx = (chunk_idx*MI_BFIELD_BITS) + idx;
mi_assert_internal(*pidx + 8 <= MI_BCHUNK_BITS);
return true;
mi_decl_maybe_unused static inline bool mi_bchunk_try_find_and_clear8_at(mi_bchunk_t* chunk, size_t chunk_idx, size_t* pidx, bool* did_temp_clear_bits) {
_Atomic(mi_bfield_t)* const bfield = &chunk->bfields[chunk_idx];
mi_bfield_t b = mi_atomic_load_relaxed(bfield);
if (b==0) return false;
int tries = 0;
do {
// has_set8 has low bit in each byte set if the byte in x == 0xFF
const mi_bfield_t has_set8 =
((~b - MI_BFIELD_LO_BIT8) & // high bit set if byte in x is 0xFF or < 0x7F
(b & MI_BFIELD_HI_BIT8)) // high bit set if byte in x is >= 0x80
>> 7; // shift high bit to low bit
size_t bitidx;
if (mi_bfield_find_least_bit(has_set8, &bitidx)) { // find least 1-bit
mi_assert_internal(bitidx <= (MI_BFIELD_BITS - 8));
mi_assert_internal((bitidx%8)==0);
if mi_likely(mi_bfield_atomic_try_clear8_optimistic(bfield,bitidx,&b,did_temp_clear_bits)) { // try to clear atomically (and update b)
*pidx = (chunk_idx*MI_BFIELD_BITS) + bitidx;
mi_assert_internal(*pidx + 8 <= MI_BCHUNK_BITS);
return true;
}
}
}
else {
return false;
}
} while(b!=0 && ++tries <= 4); // limit tries to reduce possible contention
return false;
}
@@ -715,9 +742,9 @@ mi_decl_maybe_unused static inline bool mi_bchunk_try_find_and_clear8_at(mi_bchu
// set `*pidx` to its bit index (0 <= *pidx < MI_BCHUNK_BITS) on success.
// Used to find medium size pages in the free blocks.
// todo: try neon version
static mi_decl_noinline bool mi_bchunk_try_find_and_clear8(mi_bchunk_t* chunk, size_t* pidx) {
static mi_decl_noinline bool mi_bchunk_try_find_and_clear8(mi_bchunk_t* chunk, size_t* pidx, bool* did_temp_clear_bits) {
#if MI_OPT_SIMD && defined(__AVX2__) && (MI_BCHUNK_BITS==512)
while (true) {
for(int tries=0; tries<4; tries++) { // paranoia: at most 4 tries
// since a cache-line is 64b, load all at once
const __m256i vec1 = _mm256_load_si256((const __m256i*)chunk->bfields);
const __m256i vec2 = _mm256_load_si256((const __m256i*)chunk->bfields+1);
@@ -733,25 +760,29 @@ static mi_decl_noinline bool mi_bchunk_try_find_and_clear8(mi_bchunk_t* chunk, s
const size_t chunk_idx = bidx / 8;
const size_t idx = (bidx % 8)*8;
mi_assert_internal(chunk_idx < MI_BCHUNK_FIELDS);
if mi_likely(mi_bfield_atomic_try_clear8(&chunk->bfields[chunk_idx], idx, NULL)) { // clear it atomically
if mi_likely(mi_bfield_atomic_try_clear8_optimistic(&chunk->bfields[chunk_idx], idx, NULL, did_temp_clear_bits)) { // clear it atomically
*pidx = (chunk_idx*MI_BFIELD_BITS) + idx;
mi_assert_internal(*pidx + 8 <= MI_BCHUNK_BITS);
return true;
}
// try again
// note: there must be an atomic release/acquire in between or otherwise the registers may not be reloaded }
// note: there must be an atomic release/acquire in between or otherwise the registers may not be reloaded
// we add an explicit memory barrier as older gcc compilers do not reload the registers even with an atomic acquire (issue #1206)
#if defined(__GNUC__)
__asm __volatile ("" : : "g"(chunk) : "memory");
#endif
}
#else
for (int i = 0; i < MI_BCHUNK_FIELDS; i++) {
if (mi_bchunk_try_find_and_clear8_at(chunk, i, pidx)) return true;
if (mi_bchunk_try_find_and_clear8_at(chunk, i, pidx, did_temp_clear_bits)) return true;
}
return false;
#endif
return false;
}
static inline bool mi_bchunk_try_find_and_clear_8(mi_bchunk_t* chunk, size_t n, size_t* pidx) {
mi_assert_internal(n==8); MI_UNUSED(n);
return mi_bchunk_try_find_and_clear8(chunk, pidx);
static inline bool mi_bchunk_try_find_and_clear_8(mi_bchunk_t* chunk, size_t n, size_t* pidx, bool* did_temp_clear_bits) {
mi_assert_internal(n==8); MI_UNUSED(n); MI_UNUSED(did_temp_clear_bits);
return mi_bchunk_try_find_and_clear8(chunk, pidx, did_temp_clear_bits);
}
@@ -759,7 +790,7 @@ static inline bool mi_bchunk_try_find_and_clear_8(mi_bchunk_t* chunk, size_t n,
// and try to clear them atomically.
// set `*pidx` to its bit index (0 <= *pidx <= MI_BCHUNK_BITS - n) on success.
// will cross bfield boundaries.
mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk, size_t n, size_t* pidx) {
mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk, size_t n, size_t* pidx, bool* did_temp_clear_bits) {
if (n == 0 || n > MI_BFIELD_BITS) return false;
const mi_bfield_t mask = mi_bfield_mask(n, 0);
// for all fields in the chunk
@@ -775,7 +806,7 @@ mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk,
const size_t bmask = mask<<idx;
mi_assert_internal(bmask>>idx == mask);
if ((b&bmask) == bmask) { // found a match with all bits set, try clearing atomically
if mi_likely(mi_bfield_atomic_try_clear_mask_of(&chunk->bfields[i], bmask, b0, NULL)) {
if mi_likely(mi_bfield_atomic_try_clear_mask_optimistic(&chunk->bfields[i], bmask, &b0, did_temp_clear_bits)) { // updates b0 with the previous value
*pidx = (i*MI_BFIELD_BITS) + idx;
mi_assert_internal(*pidx < MI_BCHUNK_BITS);
mi_assert_internal(*pidx + n <= MI_BCHUNK_BITS);
@@ -783,7 +814,7 @@ mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk,
}
else {
// if we failed to atomically commit, reload b and try again from the start
b = b0 = mi_atomic_load_acquire(&chunk->bfields[i]);
b = b0; // = mi_atomic_load_acquire(&chunk->bfields[i]);
}
}
else {
@@ -803,7 +834,7 @@ mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk,
if (post + pre >= n) {
// it fits -- try to claim it atomically
const size_t cidx = (i*MI_BFIELD_BITS) + (MI_BFIELD_BITS - post);
if (mi_bchunk_try_clearNX(chunk, cidx, n, NULL)) {
if (mi_bchunk_try_clearNX(chunk, cidx, n, NULL, did_temp_clear_bits)) {
// we cleared all atomically
*pidx = cidx;
mi_assert_internal(*pidx < MI_BCHUNK_BITS);
@@ -821,7 +852,7 @@ mi_decl_noinline static bool mi_bchunk_try_find_and_clearNX(mi_bchunk_t* chunk,
// and try to clear them atomically.
// set `*pidx` to its bit index (0 <= *pidx <= MI_BCHUNK_BITS - n) on success.
// This can cross bfield boundaries.
static mi_decl_noinline bool mi_bchunk_try_find_and_clearNC(mi_bchunk_t* chunk, size_t n, size_t* pidx) {
static mi_decl_noinline bool mi_bchunk_try_find_and_clearNC(mi_bchunk_t* chunk, size_t n, size_t* pidx, bool* did_temp_clear_bits) {
if (n == 0 || n > MI_BCHUNK_BITS) return false; // cannot be more than a chunk
// we first scan ahead to see if there is a range of `n` set bits, and only then try to clear atomically
@@ -870,7 +901,7 @@ static mi_decl_noinline bool mi_bchunk_try_find_and_clearNC(mi_bchunk_t* chunk,
// did we find a range?
if (m==0) {
if (mi_bchunk_try_clearN(chunk, cidx, n, NULL)) {
if (mi_bchunk_try_clearN(chunk, cidx, n, NULL, did_temp_clear_bits)) {
// we cleared all atomically
*pidx = cidx;
mi_assert_internal(*pidx < MI_BCHUNK_BITS);
@@ -888,11 +919,11 @@ static mi_decl_noinline bool mi_bchunk_try_find_and_clearNC(mi_bchunk_t* chunk,
// ------- mi_bchunk_clear_once_set ---------------------------------------
static inline void mi_bchunk_clear_once_set(mi_bchunk_t* chunk, size_t cidx) {
static inline void mi_bchunk_clear_once_set(mi_subproc_t* subproc, mi_bchunk_t* chunk, size_t cidx) {
mi_assert_internal(cidx < MI_BCHUNK_BITS);
const size_t i = cidx / MI_BFIELD_BITS;
const size_t idx = cidx % MI_BFIELD_BITS;
mi_bfield_atomic_clear_once_set(&chunk->bfields[i], idx);
mi_bfield_atomic_clear_once_set(subproc, &chunk->bfields[i], idx);
}
@@ -963,7 +994,7 @@ static bool mi_bchunk_bsr(mi_bchunk_t* chunk, size_t* pidx) {
return false;
}
static bool mi_bchunk_bsr_inv(mi_bchunk_t* chunk, size_t* pidx) {
static bool mi_bchunk_bsr_inv(mi_bchunk_t* chunk, size_t* pidx) {
for (size_t i = MI_BCHUNK_FIELDS; i > 0; ) {
i--;
mi_bfield_t b = mi_atomic_load_relaxed(&chunk->bfields[i]);
@@ -1050,7 +1081,7 @@ static void mi_bchunks_unsafe_setN(mi_bchunk_t* chunks, mi_bchunkmap_t* cmap, si
// start chunk and index
size_t chunk_idx = idx / MI_BCHUNK_BITS;
const size_t cidx = idx % MI_BCHUNK_BITS;
const size_t ccount = _mi_divide_up(n, MI_BCHUNK_BITS);
const size_t ccount = ((cidx + n - 1) / MI_BCHUNK_BITS) + 1;
// first update the chunkmap
mi_bchunk_setN(cmap, chunk_idx, ccount, NULL);
@@ -1323,9 +1354,10 @@ static bool mi_bitmap_try_find_and_claim_visit(mi_bitmap_t* bitmap, size_t chunk
return true;
}
else {
// failed to claim it, set abandoned mapping again (unless the page was freed)
// failed to claim it, set abandoned mapping again (unless the page was freed and keep_set will be false)
if (keep_set) {
const bool wasclear = mi_bchunk_set(&bitmap->chunks[chunk_idx], cidx, NULL);
mi_bitmap_chunkmap_set(bitmap, chunk_idx);
mi_assert_internal(wasclear); MI_UNUSED(wasclear);
}
}
@@ -1355,12 +1387,15 @@ bool mi_bitmap_bsr(mi_bitmap_t* bitmap, size_t* idx) {
mi_bfield_t cmap = mi_atomic_load_relaxed(&bitmap->chunkmap.bfields[i]);
size_t cmap_idx;
if (mi_bsr(cmap,&cmap_idx)) {
// highest chunk
const size_t chunk_idx = i*MI_BFIELD_BITS + cmap_idx;
size_t cidx;
if (mi_bchunk_bsr(&bitmap->chunks[chunk_idx], &cidx)) {
*idx = (chunk_idx * MI_BCHUNK_BITS) + cidx;
return true;
// from highest chunk to lowest (scan all in case the cmap entry was stale)
for (size_t j = cmap_idx+1; j>0; ) {
j--;
const size_t chunk_idx = (i*MI_BFIELD_BITS) + j;
size_t cidx;
if (mi_bchunk_bsr(&bitmap->chunks[chunk_idx], &cidx)) {
*idx = (chunk_idx * MI_BCHUNK_BITS) + cidx;
return true;
}
}
}
}
@@ -1388,12 +1423,12 @@ size_t mi_bitmap_popcount(mi_bitmap_t* bitmap) {
// Clear a bit once it is set.
void mi_bitmap_clear_once_set(mi_bitmap_t* bitmap, size_t idx) {
void mi_bitmap_clear_once_set(mi_subproc_t* subproc, mi_bitmap_t* bitmap, size_t idx) {
mi_assert_internal(idx < mi_bitmap_max_bits(bitmap));
const size_t chunk_idx = idx / MI_BCHUNK_BITS;
const size_t cidx = idx % MI_BCHUNK_BITS;
mi_assert_internal(chunk_idx < mi_bitmap_chunk_count(bitmap));
mi_bchunk_clear_once_set(&bitmap->chunks[chunk_idx], cidx);
mi_bchunk_clear_once_set(subproc, &bitmap->chunks[chunk_idx], cidx);
}
@@ -1456,6 +1491,8 @@ bool _mi_bitmap_forall_setc_ranges(mi_bitmap_t* bitmap, mi_forall_set_fun_t* vis
mi_assert_internal(rng>=1 && rng<=MI_BFIELD_BITS);
mi_assert_internal((idx % MI_BFIELD_BITS) + rng <= MI_BFIELD_BITS);
mi_assert_internal((idx / MI_BCHUNK_BITS) < mi_bitmap_chunk_count(bitmap));
// clear rng bits in b
b = b & ~mi_bfield_mask(rng, bidx);
if (!visit(idx, rng, arena, arg)) {
// break early: reset the non-visited bits
if (b!=0) {
@@ -1463,8 +1500,6 @@ bool _mi_bitmap_forall_setc_ranges(mi_bitmap_t* bitmap, mi_forall_set_fun_t* vis
}
return false;
}
// clear rng bits in b
b = b & ~mi_bfield_mask(rng, bidx);
}
mi_assert_internal(rngcount == bpopcount);
}
@@ -1502,14 +1537,15 @@ bool _mi_bitmap_forall_setc_rangesn(mi_bitmap_t* bitmap, size_t rngslices, mi_fo
const size_t base_idx = (chunk_idx*MI_BCHUNK_BITS) + (j*MI_BFIELD_BITS);
mi_bfield_t b = mi_atomic_exchange_relaxed(&chunk->bfields[j], (mi_bfield_t)0); // atomic clear
mi_bfield_t skipped = 0; // but track which bits we skip so we can restore them
for(size_t shift = 0; rngslices + shift <= MI_BFIELD_BITS; shift += rngslices) { // per `rngslices` to keep alignment
size_t shift;
for(shift = 0; rngslices + shift <= MI_BFIELD_BITS; shift += rngslices) { // per `rngslices` to keep alignment
const mi_bfield_t rngmask = mi_bfield_mask(rngslices, shift);
if ((b & rngmask) == rngmask) {
const size_t idx = base_idx + shift;
if (!visit(idx, rngslices, arena, arg)) {
// break early: restore non-visited entries
mi_bfield_t notyet_visited = 0;
if (shift + rngslices < MI_BFIELD_BITS) {
if (rngslices + shift < MI_BFIELD_BITS) {
notyet_visited = (b & (~(mi_bfield_t)0 << (shift + rngslices)));
}
mi_assert_internal((notyet_visited & skipped) == 0);
@@ -1523,8 +1559,13 @@ bool _mi_bitmap_forall_setc_rangesn(mi_bitmap_t* bitmap, size_t rngslices, mi_fo
skipped = skipped | (b & rngmask);
}
}
if (shift < MI_BFIELD_BITS) {
// there are some non-visited top bits when `MI_BFIELD_BITS % rngslices != 0`.
mi_assert_internal(MI_BFIELD_BITS % rngslices != 0);
skipped = skipped | (b & (~(mi_bfield_t)0 << shift));
}
if (skipped != 0) {
// restore non-visited entries
mi_atomic_or_relaxed(&chunk->bfields[j], skipped);
}
}
@@ -1554,7 +1595,7 @@ size_t mi_bbitmap_size(size_t bit_count, size_t* pchunk_count) {
// initialize a bitmap to all unset; avoid a mem_zero if `already_zero` is true
// returns the size of the bitmap
size_t mi_bbitmap_init(mi_bbitmap_t* bbitmap, size_t bit_count, bool already_zero) {
size_t mi_bbitmap_init(mi_subproc_t* subproc, mi_bbitmap_t* bbitmap, size_t bit_count, bool already_zero) {
size_t chunk_count;
const size_t size = mi_bbitmap_size(bit_count, &chunk_count);
if (!already_zero) {
@@ -1562,6 +1603,7 @@ size_t mi_bbitmap_init(mi_bbitmap_t* bbitmap, size_t bit_count, bool already_zer
}
mi_atomic_store_release(&bbitmap->chunk_count, chunk_count);
mi_assert_internal(mi_atomic_load_relaxed(&bbitmap->chunk_count) <= MI_BITMAP_MAX_CHUNK_COUNT);
bbitmap->subproc = subproc;
return size;
}
@@ -1572,27 +1614,16 @@ void mi_bbitmap_unsafe_setN(mi_bbitmap_t* bbitmap, size_t idx, size_t n) {
}
bool mi_bbitmap_bsr_inv(mi_bbitmap_t* bbitmap, size_t* idx) {
// scan for highest zero bit in the bitmap
// note: we cannot use the chunkmap since that only conservatively denotes if there might be a set bit in a chuck
// todo: bbitmap_init rounds up the bitcount to BCHUNK_BITS and we should skip the top-padding!
const size_t chunk_count = mi_bbitmap_chunk_count(bbitmap);
const size_t chunkmap_max = _mi_divide_up(chunk_count, MI_BFIELD_BITS);
size_t skip_at_top = chunk_count % MI_BFIELD_BITS;
for (size_t i = chunkmap_max; i > 0; ) {
for(size_t i = chunk_count; i > 0; ) {
i--;
mi_bfield_t cmap = mi_atomic_load_relaxed(&bbitmap->chunkmap.bfields[i]);
size_t cmap_idx;
// don't consider top 0 bits; set those to 1 here
if (skip_at_top > 0) {
const size_t mask_top = (~mi_bfield_zero()) << (MI_BFIELD_BITS - skip_at_top);
skip_at_top = 0; // only for the first iteration
cmap |= mask_top;
}
if (mi_bsr(~cmap, &cmap_idx)) {
// highest chunk
const size_t chunk_idx = i*MI_BFIELD_BITS + cmap_idx;
size_t cidx;
if (mi_bchunk_bsr_inv(&bbitmap->chunks[chunk_idx], &cidx)) {
*idx = (chunk_idx * MI_BCHUNK_BITS) + cidx;
return true;
}
size_t cidx;
if (mi_bchunk_bsr_inv(&bbitmap->chunks[i], &cidx)) {
*idx = (i * MI_BCHUNK_BITS) + cidx;
return true;
}
}
return false;
@@ -1609,11 +1640,11 @@ static void mi_bbitmap_set_chunk_bin(mi_bbitmap_t* bbitmap, size_t chunk_idx, mi
for (mi_chunkbin_t ibin = MI_CBIN_SMALL; ibin < MI_CBIN_NONE; ibin = mi_chunkbin_inc(ibin)) {
if (ibin == bin) {
const bool was_clear = mi_bchunk_set(& bbitmap->chunkmap_bins[ibin], chunk_idx, NULL);
if (was_clear) { mi_os_stat_increase(chunk_bins[ibin],1); }
if (was_clear) { mi_subproc_stat_increase(bbitmap->subproc, chunk_bins[ibin],1); }
}
else {
const bool was_set = mi_bchunk_clear(&bbitmap->chunkmap_bins[ibin], chunk_idx, NULL);
if (was_set) { mi_os_stat_decrease(chunk_bins[ibin],1); }
if (was_set) { mi_subproc_stat_decrease(bbitmap->subproc,chunk_bins[ibin],1); }
}
}
}
@@ -1710,8 +1741,15 @@ bool mi_bbitmap_try_clearNC(mi_bbitmap_t* bbitmap, size_t idx, size_t n) {
mi_assert_internal(chunk_idx < mi_bbitmap_chunk_count(bbitmap));
if (cidx + n > MI_BCHUNK_BITS) return false;
bool maybe_all_clear = false;
const bool cleared = mi_bchunk_try_clearN(&bbitmap->chunks[chunk_idx], cidx, n, &maybe_all_clear);
if (cleared && maybe_all_clear) { mi_bbitmap_chunkmap_try_clear(bbitmap, chunk_idx); }
bool did_temp_clear_bits = false;
const bool cleared = mi_bchunk_try_clearN(&bbitmap->chunks[chunk_idx], cidx, n, &maybe_all_clear, &did_temp_clear_bits);
if (cleared && maybe_all_clear) {
mi_assert_internal(!did_temp_clear_bits);
mi_bbitmap_chunkmap_try_clear(bbitmap, chunk_idx);
} else if (did_temp_clear_bits) {
// may have raced with a clearer (in on_find) so set the chunkmap bit conservatively
mi_bbitmap_chunkmap_set(bbitmap, chunk_idx, false);
}
// note: we don't set the size class for an explicit try_clearN (only used by purging)
return cleared;
}
@@ -1753,14 +1791,14 @@ bool mi_bbitmap_is_xsetN(mi_xset_t set, mi_bbitmap_t* bbitmap, size_t idx, size_
(used to find free pages)
-------------------------------------------------------------------------------- */
typedef bool (mi_bchunk_try_find_and_clear_fun_t)(mi_bchunk_t* chunk, size_t n, size_t* idx);
typedef bool (mi_bchunk_try_find_and_clear_fun_t)(mi_bchunk_t* chunk, size_t n, size_t* idx, bool* did_temp_clear_bits);
// Go through the bbitmap and for every sequence of `n` set bits, call the visitor function.
// If it returns `true` stop the search.
//
// This is used for finding free blocks and it is important to be efficient (with 2-level bitscan)
// but also reduce fragmentation (through size bins).
static inline bool mi_bbitmap_try_find_and_clear_generic(mi_bbitmap_t* bbitmap, size_t tseq, size_t n, size_t* pidx, mi_bchunk_try_find_and_clear_fun_t* on_find)
static bool mi_bbitmap_try_find_and_clear_generic(mi_bbitmap_t* bbitmap, size_t tseq, size_t n, size_t* pidx, mi_bchunk_try_find_and_clear_fun_t* on_find)
{
// we space out threads to reduce contention
const size_t cmap_max_count = _mi_divide_up(mi_bbitmap_chunk_count(bbitmap),MI_BFIELD_BITS);
@@ -1814,7 +1852,8 @@ static inline bool mi_bbitmap_try_find_and_clear_generic(mi_bbitmap_t* bbitmap,
mi_bchunk_t* chunk = &bbitmap->chunks[chunk_idx];
size_t cidx;
if ((*on_find)(chunk, n, &cidx)) {
bool did_temp_clear_bits = false;
if ((*on_find)(chunk, n, &cidx, &did_temp_clear_bits)) {
if (cidx==0 && ibin == MI_CBIN_NONE) { // only the first block determines the size bin
// this chunk is now reserved for the `bbin` size class
mi_bbitmap_set_chunk_bin(bbitmap, chunk_idx, bbin);
@@ -1824,9 +1863,17 @@ static inline bool mi_bbitmap_try_find_and_clear_generic(mi_bbitmap_t* bbitmap,
return true;
}
else {
// note: we may find that all are cleared only on a second iteration (when we fail to clear any bits)
// but that is ok as the chunkmap is a conservative approximation.
// todo: should _on_find_ return a boolean if there is a chance all are clear to avoid calling `try_clear?`
// we may find that all are cleared only on a second iteration but that is ok as the chunkmap is a conservative approximation.
mi_bbitmap_chunkmap_try_clear(bbitmap, chunk_idx);
// probably not as we already only call `try_clear` once we fail to clear any bits.
if (did_temp_clear_bits) {
// a concurrent find_and_claim may have cleared the chunkmap bit, restore it now
mi_bbitmap_chunkmap_set(bbitmap, chunk_idx, false);
}
else {
mi_bbitmap_chunkmap_try_clear(bbitmap, chunk_idx);
}
}
}
mi_bfield_cycle_iterate_end(Y);
@@ -1871,12 +1918,11 @@ bool mi_bbitmap_try_find_and_clearNC(mi_bbitmap_t* bbitmap, size_t tseq, size_t
// Try to atomically clear `n` bits starting at `chunk_idx` where `n` can span over multiple chunks
static bool mi_bchunk_try_clearN_(mi_bbitmap_t* bbitmap, size_t chunk_idx, size_t n) {
mi_assert_internal((chunk_idx * MI_BCHUNK_BITS) + n <= mi_bbitmap_max_bits(bbitmap));
size_t m = n; // bits to go
size_t count = 0; // chunk count
while (m > 0) {
mi_bchunk_t* chunk = &bbitmap->chunks[chunk_idx + count];
if (!mi_bchunk_try_clearN(chunk, 0, (m > MI_BCHUNK_BITS ? MI_BCHUNK_BITS : m), NULL)) {
if (!mi_bchunk_try_clearN(chunk, 0, (m > MI_BCHUNK_BITS ? MI_BCHUNK_BITS : m), NULL, NULL)) {
goto rollback;
}
m = (m <= MI_BCHUNK_BITS ? 0 : m - MI_BCHUNK_BITS);
@@ -1890,6 +1936,8 @@ rollback:
count--;
mi_bchunk_t* chunk = &bbitmap->chunks[chunk_idx + count];
mi_bchunk_setN(chunk, 0, MI_BCHUNK_BITS, NULL);
// since we may race with clearing, we need to set the chunkmap conservatively
mi_bbitmap_chunkmap_set(bbitmap, chunk_idx + count, false);
}
return false;
}
@@ -1926,7 +1974,7 @@ bool mi_bbitmap_try_find_and_clearN_(mi_bbitmap_t* bbitmap, size_t tseq, size_t
// did we find a suitable range?
if (count == chunk_req) {
// now try to claim it!
// now try to claim it!
if (mi_bchunk_try_clearN_(bbitmap, chunk_idx, n)) {
*pidx = (chunk_idx * MI_BCHUNK_BITS);
for (size_t i = 0; i < count; i++) {
@@ -1935,6 +1983,11 @@ bool mi_bbitmap_try_find_and_clearN_(mi_bbitmap_t* bbitmap, size_t tseq, size_t
mi_assert_internal(*pidx + n <= mi_bbitmap_max_bits(bbitmap));
return true;
}
else {
// contended: we reset count to retry from the first
// (we still skip the first chunk to guarantee progress)
count = 0;
}
}
// keep searching but skip the scanned range
+10 -9
View File
@@ -71,7 +71,7 @@ typedef size_t mi_bfield_t;
#define MI_BCHUNK_FIELDS (MI_BCHUNK_BITS / MI_BFIELD_BITS) // 8 on both 64- and 32-bit
// some compiler (msvc in C mode) cannot have expressions in the alignment attribute
// some compilers (msvc in C mode) cannot have expressions in the alignment attribute
#if MI_BCHUNK_SIZE==64
#define mi_decl_bchunk_align mi_decl_align(64)
#elif MI_BCHUNK_SIZE==32
@@ -95,11 +95,11 @@ typedef mi_bchunk_t mi_bchunkmap_t;
#define MI_BITMAP_MAX_CHUNK_COUNT (MI_BCHUNKMAP_BITS)
#define MI_BITMAP_MIN_CHUNK_COUNT (1)
#if MI_SIZE_BITS > 32
#define MI_BITMAP_DEFAULT_CHUNK_COUNT (64) // 2 GiB on 64-bit -- this is for the page map
#else
// #if MI_SIZE_BITS > 32
// #define MI_BITMAP_DEFAULT_CHUNK_COUNT (128) // 4 GiB on 64-bit -- this is for the page map
// #else
#define MI_BITMAP_DEFAULT_CHUNK_COUNT (1)
#endif
// #endif
#define MI_BITMAP_MAX_BIT_COUNT (MI_BITMAP_MAX_CHUNK_COUNT * MI_BCHUNK_BITS) // 16 GiB arena
#define MI_BITMAP_MIN_BIT_COUNT (MI_BITMAP_MIN_CHUNK_COUNT * MI_BCHUNK_BITS) // 32 MiB arena
#define MI_BITMAP_DEFAULT_BIT_COUNT (MI_BITMAP_DEFAULT_CHUNK_COUNT * MI_BCHUNK_BITS) // 2 GiB arena
@@ -199,7 +199,7 @@ mi_decl_nodiscard bool mi_bitmap_try_find_and_claim(mi_bitmap_t* bitmap, size_t
// Atomically clear a bit but only if it is set. Will block otherwise until the bit is set.
// This is used to delay free-ing a page that it at the same time being considered to be
// allocated from `mi_arena_try_abandoned` (and is in the `claim` function of `mi_bitmap_try_find_and_claim`).
void mi_bitmap_clear_once_set(mi_bitmap_t* bitmap, size_t idx);
void mi_bitmap_clear_once_set(mi_subproc_t* subproc, mi_bitmap_t* bitmap, size_t idx);
// If a bit is set in the bitmap, return `true` and set `idx` to the index of the highest bit.
@@ -260,8 +260,9 @@ static inline mi_chunkbin_t mi_chunkbin_of(size_t slice_count) {
typedef mi_decl_bchunk_align struct mi_bbitmap_s {
_Atomic(size_t) chunk_count; // total count of chunks (0 < N <= MI_BCHUNKMAP_BITS)
_Atomic(size_t) chunk_max_accessed; // max chunk index that was once cleared or set
#if (MI_BCHUNK_SIZE / MI_SIZE_SIZE) > 2
size_t _padding[MI_BCHUNK_SIZE/MI_SIZE_SIZE - 2]; // suppress warning on msvc by aligning manually
mi_subproc_t* subproc; // constant, for stats
#if (MI_BCHUNK_SIZE / MI_SIZE_SIZE) > 3
size_t _padding[MI_BCHUNK_SIZE/MI_SIZE_SIZE - 3]; // suppress warning on msvc by aligning manually
#endif
mi_bchunkmap_t chunkmap;
mi_bchunkmap_t chunkmap_bins[MI_CBIN_COUNT - 1]; // chunkmaps with bit set if the chunk is in that size class (excluding MI_CBIN_NONE)
@@ -288,7 +289,7 @@ bool mi_bbitmap_bsr_inv(mi_bbitmap_t* bbitmap, size_t* idx);
// Initialize a bitmap to all clear; avoid a mem_zero if `already_zero` is true
// returns the size of the bitmap.
size_t mi_bbitmap_init(mi_bbitmap_t* bbitmap, size_t bit_count, bool already_zero);
size_t mi_bbitmap_init(mi_subproc_t* subproc, mi_bbitmap_t* bbitmap, size_t bit_count, bool already_zero);
// Set/clear a sequence of `n` bits in the bitmap (and can cross chunks).
// Not atomic so only use if still local to a thread.
+388 -220
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -9,15 +9,14 @@ terms of the MIT license. A copy of the license can be found in the file
// add includes help an IDE
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim.h" // _mi_prim_thread_id()
#include "mimalloc/prim-tls.h" // _mi_prim_thread_id()
#endif
// forward declarations
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block);
static bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block);
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool was_guarded);
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block);
// forward declarations
mi_decl_nodiscard static bool mi_check_padding_on_free(const mi_page_t* page, const mi_block_t* block, bool is_guarded, size_t* usable_size);
mi_decl_nodiscard static bool mi_check_double_free(const mi_page_t* page, const mi_block_t* block);
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool was_guarded);
// ------------------------------------------------------
// Free
@@ -25,22 +24,29 @@ static void mi_stat_free(const mi_page_t* page, const mi_block_t* block);
// regular free of a (thread local) block pointer
// fast path written carefully to prevent spilling on the stack
static inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool was_guarded, bool track_stats, bool check_full)
static inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool was_guarded, bool check_full)
{
MI_UNUSED(was_guarded);
// checks
if mi_unlikely(mi_check_is_double_free(page, block)) return;
if (!was_guarded) { mi_check_padding(page, block); }
if (track_stats) { mi_stat_free(page, block); }
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN
memset(block, MI_DEBUG_FREED, mi_page_block_size(page));
#endif
if (track_stats) { mi_track_free_size(block, mi_page_usable_size_of(page, block, was_guarded)); } // faster then mi_usable_size as we already know the page and that p is unaligned
size_t usable_size;
if mi_unlikely(!mi_check_padding_on_free(page, block, was_guarded, &usable_size)) return;
if mi_unlikely(!mi_check_double_free(page,block)) return; // usually checked with padding
// mi_stat_free(page, block);
mi_track_free_size(block, usable_size);
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN
const size_t dbgsize = (usable_size > MI_MiB ? MI_MiB : usable_size);
_mi_memset_aligned(block, MI_DEBUG_FREED, dbgsize);
#endif
// actual free: push on the local free list
mi_used_t xused = page->xused;
xused.used_alloc--; // decrement used count
mi_block_set_next(page, block, page->local_free);
page->xused = xused;
page->local_free = block;
if mi_unlikely(--page->used == 0) {
mi_assert_internal(mi_page_alloc_count(page) + mi_page_last_used(page) >= mi_page_used(page));
if mi_unlikely(mi_xused_used_count(xused) == 0) { // is used count zero ?
if (page->retire_expire==0) { // no need to re-retire retired pages (happens when we alloc/free one block repeatedly in an empty page)
_mi_page_retire(page);
}
@@ -51,75 +57,67 @@ static inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool
}
// Forward declaration for multi-threaded collect
static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page, mi_block_t* mt_free) mi_attr_noexcept;
static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page, mi_block_t* mt_free, bool allow_reclaim) mi_attr_noexcept;
// Free a block multi-threaded
static inline void mi_free_block_mt(mi_page_t* page, mi_block_t* block, bool was_guarded) mi_attr_noexcept
static inline void mi_free_block_mt(mi_page_t* page, mi_block_t* block, bool was_guarded, bool allow_reclaim) mi_attr_noexcept
{
MI_UNUSED(was_guarded);
// adjust stats (after padding check and potentially recursive `mi_free` above)
mi_stat_free(page, block); // stat_free may access the padding
mi_track_free_size(block, mi_page_usable_size_of(page, block, was_guarded));
size_t usable_size;
if mi_unlikely(!mi_check_padding_on_free(page, block, was_guarded, &usable_size)) return; // checking padding is safe for mt
// adjust stats (after padding check )
// mi_stat_free(page, block); // stat_free may access the padding
mi_track_free_size(block, usable_size);
// _mi_padding_shrink(page, block, sizeof(mi_block_t));
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading
if (!was_guarded) {
size_t dbgsize = mi_usable_size(block);
if (dbgsize > MI_MiB) { dbgsize = MI_MiB; }
const size_t dbgsize = (usable_size > MI_MiB ? MI_MiB : usable_size);
_mi_memset_aligned(block, MI_DEBUG_FREED, dbgsize);
}
#endif
#endif
// push atomically on the page thread free list
// mi_theap_t* theap = _mi_page_associated_theap_peek(page);
mi_thread_free_t tf_new;
mi_thread_free_t tf_old = mi_atomic_load_relaxed(&page->xthread_free);
do {
mi_block_set_next(page, block, mi_tf_block(tf_old));
tf_new = mi_tf_create(block, true /* always use owned: try to claim it if the page is abandoned */);
} while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tf_old, tf_new)); // todo: release is enough?
tf_new = mi_tf_create(block, true);
} while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tf_old, tf_new));
// and atomically try to collect the page if it was abandoned
const bool is_owned_now = !mi_tf_is_owned(tf_old);
if (is_owned_now) {
const bool is_newly_owned = mi_tf_is_owned(tf_new) && !mi_tf_is_owned(tf_old);
if (is_newly_owned) {
mi_assert_internal(mi_page_is_abandoned(page));
mi_free_try_collect_mt(page,block);
// mi_assert_internal(!mi_page_is_abandoned_mapped(page));
mi_free_try_collect_mt(page, block, allow_reclaim);
}
}
// Adjust a block that was allocated aligned, to the actual start of the block in the page.
// note: this can be called from `mi_free_generic_mt` where a non-owning thread accesses the
// `page_start` and `block_size` fields; however these are constant and the page won't be
// `page_woffset` and `block_size` fields; however these are constant and the page won't be
// deallocated (as the block we are freeing keeps it alive) and thus safe to read concurrently.
mi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p) {
static inline mi_block_t* mi_page_ptr_unalign_ex(const mi_page_t* page, const void* p, size_t* poffset) {
mi_assert_internal(page!=NULL && p!=NULL);
const size_t diff = (uint8_t*)p - mi_page_start(page);
const size_t block_size = mi_page_block_size(page);
const size_t adjust = (_mi_is_power_of_two(block_size) ? diff & (block_size - 1) : diff % block_size);
size_t adjust = diff & (block_size - 1);
if mi_unlikely(!_mi_is_power_of_two(block_size)) {
adjust = diff % block_size;
}
if (poffset!=NULL) { *poffset = adjust; }
return (mi_block_t*)((uintptr_t)p - adjust);
}
// forward declaration for a MI_GUARDED build
#if MI_GUARDED
static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p); // forward declaration
static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {
if (mi_block_ptr_is_guarded(block, p)) {
mi_block_unguard(page, block, p);
return true;
}
else {
return false;
}
mi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p) {
return mi_page_ptr_unalign_ex(page,p,NULL);
}
#else
static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(p);
return false;
}
#endif
static inline mi_block_t* mi_validate_block_from_ptr( const mi_page_t* page, void* p ) {
static inline mi_block_t* mi_validate_block_from_ptr( const mi_page_t* page, const void* p ) {
mi_assert(_mi_page_ptr_unalign(page,p) == (mi_block_t*)p); // should never be an interior pointer
#if MI_SECURE > 0
// in secure mode we always unalign to guard against free-ing interior pointers
@@ -130,63 +128,147 @@ static inline mi_block_t* mi_validate_block_from_ptr( const mi_page_t* page, voi
#endif
}
static inline mi_block_t* mi_page_ptr_block_check(mi_page_t* page, void* p, bool* was_guarded) mi_attr_noexcept {
MI_UNUSED(was_guarded);
if mi_likely(!mi_page_has_interior_pointers(page)) {
return mi_validate_block_from_ptr(page,p);
}
else {
size_t offset;
mi_block_t* const block = mi_page_ptr_unalign_ex(page,p,&offset);
#if MI_GUARDED || MI_PROFILE
if (offset >= sizeof(mi_block_t)) {
#if MI_PROFILE
if (block->next == MI_BLOCK_TAG_PROFILED) {
_mi_page_profile_on_free(page,block,p);
}
else
#endif
#if MI_GUARDED
if (block->next == MI_BLOCK_TAG_GUARDED) {
_mi_page_block_unguard(page, block, p);
*was_guarded = true;
}
else
#endif
{ /* nothing */ }
}
#endif
return block;
}
}
// free a local pointer (page parameter comes first for better codegen)
static void mi_decl_noinline mi_free_generic_local(mi_page_t* page, void* p) mi_attr_noexcept {
mi_assert_internal(p!=NULL && page != NULL);
mi_block_t* const block = (mi_page_has_interior_pointers(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p));
const bool was_guarded = mi_block_check_unguard(page, block, p);
mi_free_block_local(page, block, was_guarded, true /* track stats */, true /* check for a full page */);
bool was_guarded = false;
mi_block_t* block = mi_page_ptr_block_check(page,p,&was_guarded);
// mi_block_t* const block = (mi_page_has_interior_pointers(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p));
// mi_block_check_profiled(page,block,p);
// const bool was_guarded = mi_block_check_unguard(page, block, p);
mi_free_block_local(page, block, was_guarded, true /* check for a full page */);
}
// free a pointer owned by another thread (page parameter comes first for better codegen)
static void mi_decl_noinline mi_free_generic_mt(mi_page_t* page, void* p) mi_attr_noexcept {
static void mi_decl_noinline mi_free_generic_mt(mi_page_t* page, void* p, bool allow_reclaim) mi_attr_noexcept {
mi_assert_internal(p!=NULL && page != NULL);
mi_block_t* const block = (mi_page_has_interior_pointers(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p));
const bool was_guarded = mi_block_check_unguard(page, block, p);
mi_free_block_mt(page, block, was_guarded);
bool was_guarded = false;
mi_block_t* block = mi_page_ptr_block_check(page,p,&was_guarded);
// mi_block_t* const block = (mi_page_has_interior_pointers(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p));
// mi_block_check_profiled(page,block,p);
// const bool was_guarded = mi_block_check_unguard(page, block, p);
mi_free_block_mt(page, block, was_guarded, allow_reclaim);
}
// generic free (for runtime integration)
void mi_decl_noinline _mi_free_generic(mi_page_t* page, bool is_local, void* p) mi_attr_noexcept {
if (is_local) mi_free_generic_local(page,p);
else mi_free_generic_mt(page,p);
else mi_free_generic_mt(page,p,true);
}
// Get the page belonging to a pointer
// Does further checks in debug mode to see if this was a valid pointer.
static inline mi_page_t* mi_validate_ptr_page(const void* p, const char* msg)
static mi_decl_forceinline bool mi_ptr_page_is_valid_ex(const void* p, const char* msg, bool free_small, bool check_p_for_null, mi_page_t** ppage)
{
MI_UNUSED_RELEASE(msg);
MI_UNUSED_RELEASE(msg); MI_UNUSED(free_small);
#if MI_DEBUG
if mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0 && !mi_option_is_enabled(mi_option_guarded_precise)) {
if mi_unlikely(!_mi_is_aligned(p,MI_SIZE_SIZE) && !mi_option_is_enabled(mi_option_guarded_precise)) {
_mi_error_message(EINVAL, "%s: invalid (unaligned) pointer: %p\n", msg, p);
return NULL;
return false;
}
mi_page_t* page = _mi_safe_ptr_page(p);
if (p != NULL && page == NULL) {
_mi_error_message(EINVAL, "%s: invalid pointer: %p\n", msg, p);
}
return page;
#else
return _mi_ptr_page(p);
#endif
mi_page_t* page;
#if MI_PAGE_META_SMALL_IS_ALIGNED
if (free_small) {
const uintptr_t up = _mi_align_down((uintptr_t)p,MI_SMALL_PAGE_SIZE); // like this for codegen on gcc
if mi_unlikely(up==0 && check_p_for_null) { return false; }
page = (mi_page_t*)up;
}
else
#endif
{
#if MI_PAGE_META_IS_ALIGNED
{ page = _mi_aligned_ptr_page0(p); }
#else
{ page = _mi_ptr_page(p); }
#endif
if mi_unlikely(page==NULL && check_p_for_null) {
#if MI_DEBUG
if (p!=NULL) { _mi_error_message(EINVAL, "%s: invalid pointer: %p\n", msg, p); }
#endif
return false;
}
}
#if MI_DEBUG
mi_page_t* const cpage = _mi_checked_ptr_page(p);
if mi_unlikely(cpage==NULL) { _mi_error_message(EINVAL, "%s: invalid pointer: %p\n", msg, p); }
#endif
#if MI_PAGE_META_IS_ALIGNED
#if MI_PAGE_META_SMALL_IS_ALIGNED
if (free_small) { mi_assert_internal(NULL == mi_atomic_load_ptr_acquire(mi_page_t,&page->self)); }
else
#elif MI_SMALL_PAGE_SIZE == MI_ARENA_SLICE_SIZE && !MI_GUARDED
// for mi_free_small we can avoid a load-acquire (but not when guarded as that may still allocate large blocks)
if (free_small) { mi_assert_internal(page == mi_atomic_load_ptr_acquire(mi_page_t,&page->self)); }
else
#endif
{ page = mi_atomic_load_ptr_acquire(mi_page_t,&page->self); }
#endif
mi_assert_internal(page!=NULL);
mi_assert(cpage==page /* page_map lookup should be the same as aligned lookup */ );
#if !MI_GUARDED
if (free_small) { mi_assert_internal(page->block_size <= MI_SMALL_MAX_OBJ_SIZE); /* free small should only be called on small pages */ }
#endif
*ppage = page;
return true;
}
static mi_decl_forceinline bool mi_ptr_page_is_valid(const void* p, const char* msg, mi_page_t** ppage) {
return mi_ptr_page_is_valid_ex(p,msg,false /* free_small? */, true /* check_p_for_null */, ppage );
}
static mi_decl_forceinline mi_page_t* mi_ptr_page_validate(const void* p, const char* msg) {
mi_page_t* page;
return (mi_ptr_page_is_valid(p,msg,&page) ? page : NULL);
}
// Free a block
// Fast path written carefully to prevent register spilling on the stack
static mi_decl_forceinline void mi_free_ex(void* p, size_t* usable, mi_page_t* page)
static mi_decl_forceinline void mi_free_nonnull(void* p, mi_page_t* page, size_t* pblock_size, bool allow_reclaim)
{
if mi_unlikely(page==NULL) return; // page will be NULL if p==NULL
mi_assert_internal(p!=NULL && page!=NULL);
if (usable!=NULL) { *usable = mi_page_usable_block_size(page); }
if (pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
const mi_threadid_t xtid = (_mi_prim_thread_id() ^ mi_page_xthread_id(page));
const mi_threadid_t ptid = mi_page_xthread_id(page);
const mi_threadid_t xtid = (_mi_prim_thread_id() ^ ptid);
if mi_likely(xtid == 0) { // `tid == mi_page_thread_id(page) && mi_page_flags(page) == 0`
// thread-local, aligned, and not a full page
mi_block_t* const block = mi_validate_block_from_ptr(page,p);
mi_free_block_local(page, block, false /* was guarded */, true /* track stats */, false /* no need to check if the page is full */);
mi_free_block_local(page, block, false /* was guarded */, false /* no need to check if the page is full */);
}
else if (xtid <= MI_PAGE_FLAG_MASK) { // `tid == mi_page_thread_id(page) && mi_page_flags(page) != 0`
// page is local, but is full or contains (inner) aligned blocks; use generic path
@@ -196,45 +278,125 @@ static mi_decl_forceinline void mi_free_ex(void* p, size_t* usable, mi_page_t* p
else if ((xtid & MI_PAGE_FLAG_MASK) == 0) { // `tid != mi_page_thread_id(page) && mi_page_flags(page) == 0`
// blocks are aligned (and not a full page); push on the thread_free list
mi_block_t* const block = mi_validate_block_from_ptr(page,p);
mi_free_block_mt(page,block,false /* was_guarded */);
mi_free_block_mt(page,block,false /* was_guarded */, allow_reclaim);
}
else {
// page is full or contains (inner) aligned blocks; use generic multi-thread path
mi_free_generic_mt(page, p);
mi_free_generic_mt(page, p, allow_reclaim);
}
}
void mi_free(void* p) mi_attr_noexcept {
mi_page_t* const page = mi_validate_ptr_page(p,"mi_free");
mi_free_ex(p, NULL, page);
void mi_free(void* p) mi_attr_noexcept {
mi_page_t* page;
if mi_likely(mi_ptr_page_is_valid(p,"mi_free",&page)) {
mi_free_nonnull(p, page, NULL, true /* allow reclaim? */);
}
}
void mi_ufree(void* p, size_t* usable) mi_attr_noexcept {
mi_page_t* const page = mi_validate_ptr_page(p,"mi_ufree");
mi_free_ex(p, usable, page);
void mi_ufree(void* p, size_t* pblock_size) mi_attr_noexcept {
mi_page_t* page;
if mi_likely(mi_ptr_page_is_valid(p,"mi_ufree",&page)) {
mi_free_nonnull(p, page, pblock_size, true /* allow reclaim? */);
}
else {
if (pblock_size!=NULL) { *pblock_size = 0; }
}
}
void mi_free_small(void* p) mi_attr_noexcept {
// We can only call `mi_free_small` for pointers allocated with `mi_(heap_)malloc_small`.
// If we keep page info in front of the page area for small objects, we can find the info
// just by aligning down the pointer instead of looking it up in the page map.
#if MI_PAGE_META_ALIGNED_FREE_SMALL
#if MI_GUARDED
#warning "MI_PAGE_META_ALIGNED_FREE_SMALL ignored as MI_GUARDED is defined"
mi_page_t* page;
if mi_likely(mi_ptr_page_is_valid_ex(p,"mi_free_small",true /* is_small? */,true /*check p for null*/, &page)) {
mi_free_nonnull(p, page, NULL, true /* allow reclaim? */);
}
}
void mi_free_small_nonnull(void* p) mi_attr_noexcept {
mi_assert(p!=NULL);
mi_page_t* page;
if mi_likely(mi_ptr_page_is_valid_ex(p,"mi_free_small_nonnull",true /* is_small? */,false /*check p for null*/, &page)) {
mi_free_nonnull(p, page, NULL, true /* allow reclaim? */);
}
}
// Free a pointer that is potentially allocated in a different sub-process
void _mi_free_subproc_safe(void* p) mi_attr_noexcept {
mi_page_t* page;
if mi_likely(mi_ptr_page_is_valid(p,"_mi_free_subproc_safe",&page)) {
// const bool allow_reclaim = (_mi_subproc() == mi_page_subproc(page));
mi_free_nonnull(p, page, NULL, false /* allow reclaim */);
}
}
// ------------------------------------------------------
// Free variants
// ------------------------------------------------------
void mi_free_size(void* p, size_t size) mi_attr_noexcept {
MI_UNUSED_RELEASE(size);
#if MI_DEBUG
const mi_page_t* const page = mi_ptr_page_validate(p,"mi_free_size");
if (page==NULL) return;
mi_assert(p!=NULL);
const mi_block_t* block = _mi_page_ptr_unalign(page, p);
const size_t usable = _mi_page_usable_size(page,p);
if mi_unlikely(size > usable) {
const bool is_guarded = mi_block_ptr_is_guarded(block,p);
if (!is_guarded) {
_mi_error_message(EINVAL, "pointer %p is freed with mi_free_size but the size %zu is greater than the usable size %zu\n", p, size, usable);
mi_free(p);
return;
}
}
// const size_t is_aligned = ((void*)block != p);
// if mi_unlikely(size <= MI_SMALL_SIZE_MAX && mi_page_block_size(page) > mi_good_size((is_aligned ? 2 : 1)*MI_SMALL_SIZE_MAX)) { // note: we check *2 in case it was over-aligned
// const bool is_guarded = mi_block_ptr_is_guarded(block,p);
// if (!is_guarded) {
// _mi_error_message(EINVAL, "pointer %p is freed with mi_free_size but the given size %zu is less than the allocated block size %zu\n (maybe a `new[]` was matched with `delete` instead of `delete[]`?)\n", p, size, mi_page_block_size(page));
// mi_free(p);
// return;
// }
// }
#endif
#if MI_PAGE_META_SMALL_IS_ALIGNED || MI_PAGE_META_IS_ALIGNED
if mi_likely(size <= MI_SMALL_SIZE_MAX) {
mi_free_small(p);
}
else
#endif
{
mi_free(p);
#elif MI_ARENA_SLICE_ALIGN < MI_SMALL_PAGE_SIZE
#warning "MI_PAGE_META_ALIGNED_FREE_SMALL ignored as the MI_ARENA_SLICE_ALIGN is less than the small page size"
}
}
void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept {
mi_assert(((uintptr_t)p % alignment) == 0);
// If the alignment is smaller than the `size`, then for `size <= MI_SMALL_SIZE_MAX`
// the block must be allocated within a small page and can thus be handled safely by `mi_free_size`.
// (since even with over-allocation the block size will be less than 2*MI_SMALL_SIZE_MAX <= MI_SMALL_OBJ_SIZE_MAX)
if mi_likely(alignment <= size) {
mi_free_size(p,size);
}
else {
mi_free(p);
#else
mi_page_t* const page = (mi_page_t*)_mi_align_down_ptr(p,MI_SMALL_PAGE_SIZE);
mi_assert(page == mi_validate_ptr_page(p,"mi_free_small"));
mi_assert((void*)page == _mi_align_down_ptr(page->page_start,MI_SMALL_PAGE_SIZE));
mi_assert(page->block_size <= MI_SMALL_SIZE_MAX); // note: not `MI_SMALL_MAX_OBJ_SIZE` as we need to match `mi_(heap_)malloc_small`
mi_free_ex(p, NULL, page);
#endif
#else
}
}
void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept {
MI_UNUSED_RELEASE(alignment);
mi_assert(((uintptr_t)p % alignment) == 0);
mi_free(p);
#endif
}
// checked free
bool mi_cfree(void* p) mi_attr_noexcept {
mi_page_t* const page = _mi_checked_ptr_page(p);
if mi_likely(page!=NULL) {
mi_free_nonnull(p, page, NULL, true /* allow reclaim? */);
return true;
}
else {
return false;
}
}
@@ -270,14 +432,14 @@ static bool mi_abandoned_page_try_reabandon_to_mapped(mi_page_t* page)
// Release ownership of a page. This may free or reabandoned the page if other blocks are concurrently
// freed in the meantime. Returns `true` if the page was freed.
// By passing the captured `expected_thread_free`, we can often avoid calling `mi_page_free_collect`.
static void mi_abandoned_page_unown_from_free(mi_page_t* page, mi_block_t* expected_thread_free) {
static void mi_abandoned_page_unown_from_free(mi_page_t* page, mi_block_t* page_thread_free) {
mi_assert_internal(mi_page_is_owned(page));
mi_assert_internal(mi_page_is_abandoned(page));
mi_assert_internal(!mi_page_all_free(page));
// try to cas atomically the original free list (`mt_free`) back with the ownership cleared.
mi_thread_free_t tf_expect = mi_tf_create(expected_thread_free, true);
mi_thread_free_t tf_new = mi_tf_create(expected_thread_free, false);
while mi_unlikely(!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tf_expect, tf_new)) {
mi_thread_free_t tf_expect = mi_tf_create(page_thread_free,true);
mi_thread_free_t tf_new = mi_tf_set_owned(tf_expect,false);
while mi_unlikely(!mi_atomic_cas_strong_acq_rel(&page->xthread_free, &tf_expect, tf_new)) {
mi_assert_internal(mi_tf_is_owned(tf_expect));
// while the xthread_free list is not empty..
while (mi_tf_block(tf_expect) != NULL) {
@@ -286,9 +448,10 @@ static void mi_abandoned_page_unown_from_free(mi_page_t* page, mi_block_t* expec
if (mi_abandoned_page_try_free(page)) return;
if (mi_abandoned_page_try_reabandon_to_mapped(page)) return;
// otherwise continue un-owning
tf_expect = mi_atomic_load_relaxed(&page->xthread_free);
tf_expect = mi_atomic_load_acquire(&page->xthread_free);
}
// and try again to release ownership
mi_subproc_stat_counter_increase(mi_page_subproc(page), pages_unabandon_busy_wait, 1);
mi_assert_internal(mi_tf_block(tf_expect)==NULL);
tf_new = mi_tf_create(NULL, false);
}
@@ -315,14 +478,14 @@ static mi_decl_noinline bool mi_abandoned_page_try_reclaim(mi_page_t* page, long
mi_assert_internal(!mi_page_all_free(page));
mi_assert_internal(page->block_size <= MI_MEDIUM_MAX_OBJ_SIZE);
mi_assert_internal(reclaim_on_free >= 0);
// dont reclaim if we just have terminated this thread and we should
// not reinitialize the theap for this thread. (can happen due to thread-local destructors for example -- issue #944)
if (!_mi_thread_is_initialized()) return false;
// get our theap
mi_theap_t* const theap = _mi_page_associated_theap_peek(page);
if (theap==NULL || !theap->allow_page_reclaim) return false;
if (theap==NULL || theap->tld==NULL || !theap->allow_page_reclaim) return false; // see issue #1289
// todo: cache `is_in_threadpool` and `exclusive_arena` directly in the theap for performance?
// set max_reclaim limit
@@ -354,25 +517,27 @@ static mi_decl_noinline bool mi_abandoned_page_try_reclaim(mi_page_t* page, long
// We freed a block in an abandoned page (that was not owned). Try to collect
static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page, mi_block_t* mt_free) mi_attr_noexcept
static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page, mi_block_t* mt_free, bool allow_reclaim) mi_attr_noexcept
{
mi_assert_internal(mi_page_is_owned(page));
mi_assert_internal(mi_page_is_abandoned(page));
mi_assert_internal(mt_free != NULL);
// mi_assert_internal(!allow_reclaim || _mi_subproc() == mi_page_subproc(page)); // never reclaim across subprocesses
// we own the page now, and it is safe to collect the thread atomic free list
if (page->block_size <= MI_SMALL_SIZE_MAX) {
// use the `_partly` version to avoid atomic operations since we already have the `mt_free` pointing into the thread free list
// (after this the `used` count might be too high (as some blocks may have been concurrently added to the thread free list and are yet uncounted).
// however, if the page became completely free, the used count is guaranteed to be 0.)
mi_assert_internal(page->reserved>=16); // below this even one freed block goes from full to no longer mostly used.
_mi_page_free_collect_partly(page, mt_free);
mt_free = _mi_page_free_collect_partly(page, mt_free);
}
else {
// for larger blocks we use the regular collect
_mi_page_free_collect(page,false /* no force */);
mt_free = NULL; // expected page->xthread_free value after collection
}
const long reclaim_on_free = _mi_option_get_fast(mi_option_page_reclaim_on_free);
const long reclaim_on_free = allow_reclaim && _mi_option_get_fast(mi_option_page_reclaim_on_free);
#if MI_DEBUG > 1
if (mi_page_is_singleton(page)) { mi_assert_internal(mi_page_all_free(page)); }
if (mi_page_is_full(page)) { mi_assert(mi_page_is_mostly_used(page)); }
@@ -391,112 +556,98 @@ static void mi_decl_noinline mi_free_try_collect_mt(mi_page_t* page, mi_block_t*
// ------------------------------------------------------
// Usable size
// Usable size
// ------------------------------------------------------
// Bytes available in a block
static size_t mi_decl_noinline mi_page_usable_aligned_size_of(const mi_page_t* page, const void* p) mi_attr_noexcept {
const mi_block_t* block = _mi_page_ptr_unalign(page, p);
static size_t mi_decl_noinline mi_page_usable_aligned_size_of(const mi_page_t* page, const void* p, const mi_block_t* block) mi_attr_noexcept {
// const mi_block_t* block = _mi_page_ptr_unalign(page, p);
const bool is_guarded = mi_block_ptr_is_guarded(block,p);
const size_t size = mi_page_usable_size_of(page, block, is_guarded);
const ptrdiff_t adjust = (uint8_t*)p - (uint8_t*)block;
mi_assert_internal(adjust >= 0 && (size_t)adjust <= size);
const size_t aligned_size = (size - adjust);
mi_assert_internal((void*)p >= (void*)block);
const size_t adjust = (uint8_t*)p - (uint8_t*)block;
mi_assert_internal(adjust <= size);
const size_t aligned_size = (adjust <= size ? size - adjust : 0); // size can be zero if the padding is corrupted
return aligned_size;
}
static inline size_t _mi_usable_size(const void* p, const mi_page_t* page) mi_attr_noexcept {
size_t _mi_page_usable_size(const mi_page_t* page, const void* p) mi_attr_noexcept {
if mi_unlikely(page==NULL) return 0;
if mi_likely(!mi_page_has_interior_pointers(page)) {
const mi_block_t* block = (const mi_block_t*)p;
mi_assert_internal(mi_ptr_page_validate(p,"_mi_page_usable_size") == page);
const mi_block_t* block = _mi_page_ptr_unalign(page, p); // for safety, always unalign
if mi_likely((const void*)block==page) {
return mi_page_usable_size_of(page, block, false /* is guarded */);
}
else {
// split out to separate routine for improved code generation
return mi_page_usable_aligned_size_of(page, p);
return mi_page_usable_aligned_size_of(page, p, block);
}
}
mi_decl_nodiscard size_t mi_usable_size(const void* p) mi_attr_noexcept {
const mi_page_t* const page = mi_validate_ptr_page(p,"mi_usable_size");
return _mi_usable_size(p,page);
}
// ------------------------------------------------------
// Free variants
// ------------------------------------------------------
void mi_free_size(void* p, size_t size) mi_attr_noexcept {
MI_UNUSED_RELEASE(size);
#if MI_DEBUG
const mi_page_t* const page = mi_validate_ptr_page(p,"mi_free_size");
const size_t available = _mi_usable_size(p,page);
mi_assert(p == NULL || size <= available || available == 0 /* invalid pointer */ );
#endif
mi_free(p);
}
void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept {
MI_UNUSED_RELEASE(alignment);
mi_assert(((uintptr_t)p % alignment) == 0);
mi_free_size(p,size);
}
void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept {
MI_UNUSED_RELEASE(alignment);
mi_assert(((uintptr_t)p % alignment) == 0);
mi_free(p);
const mi_page_t* const page = mi_ptr_page_validate(p,"mi_usable_size");
return _mi_page_usable_size(page,p);
}
// ------------------------------------------------------
// Deprecated: double free is usually checked with padding now
// as that is faster and works better for cross-thread free'ing.
// Check for double free in secure and debug mode
// This is somewhat expensive so only enabled for secure mode 4
// ------------------------------------------------------
#if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0))
#if MI_SECURE>=3 && !MI_PADDING
// linear check if the free list contains a specific element
static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem) {
while (list != NULL) {
static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem, const char* list_kind) {
const size_t max_count = page->capacity; // can never hold more blocks than the capacity
size_t count = 0;
while (list != NULL && count <= max_count) { // double-free can create cycles so we limit the number of iterations
if (elem==list) return true;
list = mi_block_next(page, list);
count++;
}
if mi_unlikely(count > max_count) {
_mi_error_message(EFAULT, "corrupted %s list (possibly due to a double free)\n", list_kind);
}
return false;
}
static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) {
static mi_decl_noinline bool mi_check_double_freex(const mi_page_t* page, const mi_block_t* block) {
// The decoded value is in the same page (or NULL).
// Walk the free lists to verify positively if it is already freed
if (mi_list_contains(page, page->free, block) ||
mi_list_contains(page, page->local_free, block) ||
mi_list_contains(page, mi_page_thread_free(page), block))
if (mi_list_contains(page, page->free, block, "free") ||
mi_list_contains(page, page->local_free, block, "local free") ||
mi_list_contains(page, mi_page_thread_free(page), block, "thread free"))
{
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page));
return true;
return false;
}
return false;
return true;
}
#define mi_track_page(page,access) { size_t psize; void* pstart = _mi_page_start(_mi_page_segment(page),page,&psize); mi_track_mem_##access( pstart, psize); }
// Used for double free checking to avoid checking free lists too frequently
static inline bool mi_block_could_be_double_free(const mi_page_t* page, const mi_block_t* block) {
mi_block_t* n = mi_block_nextx(page,block,page->keys);
return (_mi_is_aligned(block,MI_SIZE_SIZE) && // quick check: aligned pointer?
(n==NULL || mi_page_contains_address(page,n))); // quick check: in the same page or NULL?
}
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
bool is_double_free = false;
mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field
if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer?
(n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL?
// check if `block` was free'd before
static inline bool mi_check_double_free(const mi_page_t* page, const mi_block_t* block) {
if mi_unlikely(mi_block_could_be_double_free(page,block)) // quick check: next field is aligned in the same page or NULL?
{
// Suspicious: decoded value a in block is in the same page (or NULL) -- maybe a double free?
// (continue in separate function to improve code generation)
is_double_free = mi_check_is_double_freex(page, block);
return mi_check_double_freex(page, block);
}
return is_double_free;
else return true;
}
#else
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
static inline bool mi_check_double_free(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page);
MI_UNUSED(block);
return false;
return true;
}
#endif
@@ -506,30 +657,31 @@ static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block
// ---------------------------------------------------------------------------
#if MI_PADDING // && !MI_TRACK_ENABLED
static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
static inline bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize, bool* double_free) {
*bsize = mi_page_usable_block_size(page);
const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
mi_track_mem_defined(padding,sizeof(mi_padding_t));
*delta = padding->delta;
uint32_t canary = padding->canary;
uintptr_t keys[2];
keys[0] = page->keys[0];
keys[1] = page->keys[1];
bool ok = (mi_ptr_encode_canary(page,block,keys) == canary && *delta <= *bsize);
const uint32_t canary = padding->canary;
const bool ok = (mi_ptr_encode_canary(page,block,page->keys) == canary && *delta <= *bsize);
if (double_free!=NULL) {
if mi_unlikely(!ok) { *double_free = mi_ptr_decode_canary_is_freed(canary); } // double free?
else { padding->canary = mi_ptr_encode_canary_freed(); } // mark as freed
}
mi_track_mem_noaccess(padding,sizeof(mi_padding_t));
return ok;
}
// Return the exact usable size of a block.
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) {
if (is_guarded) {
if mi_unlikely(is_guarded) {
const size_t bsize = mi_page_block_size(page);
return (bsize - _mi_os_page_size());
}
else {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
bool ok = mi_page_decode_padding(page, block, &delta, &bsize, NULL);
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
return (ok ? bsize - delta : 0);
}
@@ -540,9 +692,10 @@ static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* bl
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
// so it will later not trigger an overflow error in `mi_free_block`.
void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
if (mi_block_ptr_is_guarded(block,block + 1)) return;
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
bool ok = mi_page_decode_padding(page, block, &delta, &bsize, NULL);
mi_assert_internal(ok);
if (!ok || (bsize - delta) >= min_size) return; // usually already enough space
mi_assert_internal(bsize >= min_size);
@@ -555,9 +708,15 @@ void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const si
mi_track_mem_noaccess(padding,sizeof(mi_padding_t));
}
#else
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) {
MI_UNUSED(is_guarded); MI_UNUSED(block);
return mi_page_usable_block_size(page);
static inline size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) {
MI_UNUSED(block);
if mi_unlikely(is_guarded) {
const size_t bsize = mi_page_block_size(page);
return (bsize - _mi_os_page_size());
}
else {
return mi_page_usable_block_size(page);
}
}
void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
@@ -565,16 +724,17 @@ void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const si
}
#endif
#if MI_PADDING && MI_PADDING_CHECK
#if MI_PADDING
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong, bool* is_double_free) {
size_t bsize;
size_t delta;
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
bool ok = mi_page_decode_padding(page, block, &delta, &bsize, is_double_free );
*size = *wrong = bsize;
if (!ok) return false;
mi_assert_internal(bsize >= delta);
*size = bsize - delta;
#if MI_PADDING_CHECK_BYTES
if (!mi_page_is_huge(page)) {
uint8_t* fill = (uint8_t*)block + bsize - delta;
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
@@ -588,41 +748,65 @@ static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, si
}
mi_track_mem_noaccess(fill, maxpad);
}
#endif
return ok;
}
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
size_t size;
size_t wrong;
if (!mi_verify_padding(page,block,&size,&wrong)) {
_mi_error_message(EFAULT, "buffer overflow in theap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
mi_decl_nodiscard static bool mi_check_padding_on_free(const mi_page_t* page, const mi_block_t* block, bool is_guarded, size_t* usable_size) {
if mi_unlikely(is_guarded) {
const size_t bsize = mi_page_block_size(page);
*usable_size = (bsize - _mi_os_page_size());
return true;
}
else {
size_t wrong;
bool is_double_free;
if mi_unlikely(!mi_verify_padding(page,block,usable_size,&wrong,&is_double_free)) {
if (is_double_free) {
_mi_error_message(EAGAIN, "double free detected of heap block %p with size %zu\n", block, *usable_size);
}
else {
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, *usable_size, wrong );
}
return false;
}
return true;
}
}
#else
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page);
MI_UNUSED(block);
mi_decl_nodiscard static bool mi_check_padding_on_free(const mi_page_t* page, const mi_block_t* block, bool is_guarded, size_t* usable_size) {
*usable_size = mi_page_usable_size_of(page,block,is_guarded);
return true;
}
#endif
// only maintain stats for smaller objects if requested
#if (MI_STAT>0)
#if 0 && (MI_STATS>0)
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(block);
mi_theap_t* const theap = _mi_theap_default();
if (!mi_theap_is_initialized(theap)) return; // (for now) skip statistics if free'd after thread_done was called (usually a thread cleanup call by the OS)
MI_UNUSED(block);
mi_theap_t* theap = _mi_theap_default();
mi_lock_t* lock = NULL;
mi_subproc_t* const subproc = mi_page_subproc(page);
mi_theap_t* const theap_meta = subproc->theap_meta;
if mi_unlikely(!mi_theap_is_initialized(theap) || // can happen if free'd after thread_done was called (usually a thread cleanup call by the OS)
// page->theap == subproc->theap_meta .. but we cannot read `theap` if we don't own the page
(theap_meta != NULL && mi_page_thread_id(page) == theap_meta->tld->thread_id)) {
theap = theap_meta;
lock = &subproc->theap_meta_lock;
mi_lock_acquire(lock);
}
const size_t bsize = mi_page_usable_block_size(page);
// #if (MI_STAT>1)
// #if (MI_STATS>=2)
// const size_t usize = mi_page_usable_size_of(page, block);
// mi_theap_stat_decrease(theap, malloc_requested, usize);
// #endif
if (bsize <= MI_LARGE_MAX_OBJ_SIZE) {
mi_theap_stat_decrease(theap, malloc_normal, bsize);
#if (MI_STAT > 1)
#if (MI_STATS > 1)
mi_theap_stat_decrease(theap, malloc_bins[_mi_bin(bsize)], 1);
#endif
}
@@ -630,29 +814,13 @@ static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
const size_t bpsize = mi_page_block_size(page); // match stat in page.c:mi_huge_page_alloc
mi_theap_stat_decrease(theap, malloc_huge, bpsize);
}
if mi_unlikely(lock!=NULL) {
mi_lock_release(lock);
}
}
#else
void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
MI_UNUSED(page); MI_UNUSED(block);
}
#endif
// Remove guard page when building with MI_GUARDED
#if MI_GUARDED
static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p) {
MI_UNUSED(p);
mi_assert_internal(mi_block_ptr_is_guarded(block, p));
mi_assert_internal(mi_page_has_interior_pointers(page));
mi_assert_internal((uint8_t*)p - (uint8_t*)block >= (ptrdiff_t)sizeof(mi_block_t));
mi_assert_internal(block->next == MI_BLOCK_TAG_GUARDED);
const size_t bsize = mi_page_block_size(page);
const size_t psize = _mi_os_page_size();
mi_assert_internal(bsize > psize);
mi_assert_internal(!page->memid.is_pinned);
void* gpage = (uint8_t*)block + bsize - psize;
mi_assert_internal(_mi_is_aligned(gpage, psize));
_mi_os_unprotect(gpage, psize);
}
#endif
+144 -106
View File
@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -7,7 +7,8 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim.h" // _mi_theap_default
#include "mimalloc/prim.h" // _mi_prim_thread_yield
#include "mimalloc/prim-tls.h" // _mi_heap_theap
/* -----------------------------------------------------------
@@ -15,7 +16,7 @@ terms of the MIT license. A copy of the license can be found in the file
----------------------------------------------------------- */
mi_theap_t* mi_heap_theap(mi_heap_t* heap) {
return _mi_heap_theap(heap);
return _mi_heap_theap(heap); // in prim.h
}
void mi_heap_set_numa_affinity(mi_heap_t* heap, int numa_node) {
@@ -30,64 +31,66 @@ void mi_heap_stats_merge_to_subproc(mi_heap_t* heap) {
void mi_heap_stats_merge_to_main(mi_heap_t* heap) {
if (heap==NULL) return;
_mi_stats_merge_into(&mi_heap_main()->stats, &heap->stats);
_mi_stats_merge_into(&mi_heap_get_heap_main(heap)->stats, &heap->stats);
}
bool _mi_heap_theap_set(mi_heap_t* heap, mi_theap_t* theap) {
mi_assert_internal((uintptr_t)theap == 1 || _mi_theap_heap(theap)==heap);
mi_assert_internal(!_mi_is_empty_theap(theap));
mi_assert_internal(heap->theap != 0);
return _mi_thread_local_set(heap->theap,theap);
}
// mi_theap_t* _mi_heap_theap_get_peek(const mi_heap_t* heap) {
// mi_theap_t* theap;
// mi_assert_internal(heap->theap != 0);
// if mi_likely(heap->theap!=0) { // paranoia
// theap = (mi_theap_t*)_mi_thread_local_get(heap->theap);
// }
// else {
// _mi_error_message(EFAULT, "no thread-local reserved for heap (%p)\n", heap);
// return NULL;
// }
// mi_assert_internal(!_mi_is_empty_theap(theap));
// mi_assert_internal(theap->heap == heap); // this goes wrong if using main heaps across subprocesses (as all share the same key)
// return theap;
// }
static mi_decl_noinline mi_theap_t* mi_heap_init_theap(const mi_heap_t* const_heap)
{
mi_heap_t* heap = (mi_heap_t*)const_heap;
mi_assert_internal(heap!=NULL);
if (_mi_is_heap_main(heap)) {
// this can be called if the (main) thread is not yet initialized (as no allocation happened)
// but `theap_main_init_get()` will call `mi_thread_init()`
mi_theap_t* const theap = _mi_theap_main_safe();
mi_assert_internal(theap!=NULL && _mi_is_heap_main(_mi_theap_heap(theap)));
return theap;
// initialize thread first in case this is the main heap
// (which may allocate the default theap already for the main heap)
if (!_mi_thread_is_initialized()) {
mi_thread_init();
}
// otherwise initialize the theap for this heap
// get the thread local
mi_assert_internal(heap->theap != 0);
if (heap->theap==0) { // paranoia
_mi_error_message(EFAULT, "no thread-local reserved for heap (%p)\n", heap);
return NULL;
}
// get the thread local theap
mi_theap_t* theap = (mi_theap_t*)_mi_thread_local_get(heap->theap);
// create a fresh theap?
if (theap==NULL) {
// set first an invalid value to ensure the thread local storage is allocated
if (!_mi_thread_local_set(heap->theap, (mi_theap_t*)1)) {
_mi_error_message(EFAULT, "unable to allocate memory for thread local storage\n");
return NULL;
}
// then allocate the theap
theap = _mi_theap_create(heap, _mi_theap_default_safe()->tld);
_mi_thread_local_set(heap->theap, theap); // Cannot fail now as it was set before. Always set so the local is valid or NULL (and not 1)
// allocate a fresh theap
theap = _mi_theap_create(heap, mi_theap_get_default()->tld); // sets the theap thread local
if (theap==NULL) {
_mi_error_message(EFAULT, "unable to allocate memory for a thread local heap\n");
return NULL;
}
}
_mi_heap_theap_set(heap, theap);
mi_assert_internal(theap == (mi_theap_t*)_mi_thread_local_get(heap->theap));
}
return theap;
}
// get the theap for a heap without initializing (and return NULL in that case)
mi_theap_t* _mi_heap_theap_get_peek(const mi_heap_t* heap) {
if (heap==NULL || _mi_is_heap_main(heap)) {
return _mi_theap_main_safe();
}
else {
return (mi_theap_t*)_mi_thread_local_get(heap->theap);
}
}
// get (and possibly create) the theap belonging to a heap
mi_theap_t* _mi_heap_theap_get_or_init(const mi_heap_t* heap)
mi_decl_cold mi_theap_t* _mi_heap_theap_get_or_init(const mi_heap_t* heap)
{
mi_theap_t* theap = _mi_heap_theap_peek(heap);
mi_assert_internal(heap->theap != 0);
mi_theap_t* theap = (mi_theap_t*)_mi_thread_local_get(heap->theap);
if mi_unlikely(theap==NULL) {
theap = mi_heap_init_theap(heap);
if (theap==NULL) { return (mi_theap_t*)&_mi_theap_empty_wrong; } // this will return NULL from page.c:_mi_malloc_generic
@@ -96,28 +99,15 @@ mi_theap_t* _mi_heap_theap_get_or_init(const mi_heap_t* heap)
return theap;
}
mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t exclusive_arena_id) {
// always allocate heap data in the (subprocess) main heap
mi_heap_t* const heap_main = mi_heap_main();
// todo: allocate heap data in the exclusive arena ?
mi_heap_t* const heap = (mi_heap_t*)mi_heap_zalloc( heap_main, sizeof(mi_heap_t) );
if (heap==NULL) return NULL;
// reserve a thread local slot for this heap (see also issue #1230)
const mi_thread_local_t theap_slot = _mi_thread_local_create();
if (theap_slot == 0) {
_mi_error_message(EFAULT, "unable to dynamically create a thread local for a heap\n");
mi_free(heap);
return NULL;
}
void _mi_heap_init(mi_heap_t* heap, mi_thread_local_t theap_slot, mi_subproc_t* subproc, mi_arena_id_t exclusive_arena_id)
{
// init fields
heap->theap = theap_slot;
heap->subproc = heap_main->subproc;
heap->heap_seq = mi_atomic_increment_relaxed(&heap_main->subproc->heap_total_count);
heap->subproc = subproc;
heap->heap_seq = mi_atomic_increment_relaxed(&subproc->heap_total_count);
heap->exclusive_arena = _mi_arena_from_id(exclusive_arena_id);
heap->numa_node = -1; // no initial affinity
heap->profiler = mi_atomic_load_ptr_acquire(mi_profiler_t,&subproc->profiler);
mi_stats_header_init(&heap->stats);
mi_lock_init(&heap->theaps_lock);
mi_lock_init(&heap->os_abandoned_pages_lock);
@@ -131,94 +121,141 @@ mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t exclusive_arena_id) {
if (head!=NULL) { head->prev = heap; }
heap->subproc->heaps = heap;
}
mi_atomic_increment_relaxed(&heap_main->subproc->heap_count);
mi_subproc_stat_increase(heap_main->subproc, heaps, 1);
mi_atomic_increment_relaxed(&subproc->heap_count);
mi_subproc_stat_increase(subproc, heaps, 1);
mi_assert_internal(_mi_is_heap_main(heap) ? heap->theap == mi_thread_local_key_fast : heap->theap != 0);
}
mi_heap_t* _mi_heap_new_for_subproc(mi_subproc_t* subproc, mi_arena_id_t exclusive_arena_id, bool is_main_heap) {
mi_assert_internal(is_main_heap ? (subproc->heap_main == NULL && subproc->parent != NULL) : subproc->heap_main != NULL);
// heap data is allocated in the current subproc
mi_heap_t* const heap_main = (is_main_heap ? subproc->parent->heap_main : subproc->heap_main);
// todo: allocate heap data in the exclusive arena ?
mi_heap_t* const heap = (mi_heap_t*)mi_heap_zalloc( heap_main, sizeof(mi_heap_t) );
if (heap==NULL) return NULL;
// reserve a thread local slot for this heap (see also issue #1230)
mi_thread_local_t theap_slot = (is_main_heap ? mi_thread_local_key_fast : _mi_thread_local_create());
if (theap_slot == 0) {
_mi_error_message(EFAULT, "unable to dynamically create a thread local for a heap\n");
mi_free(heap);
return NULL;
}
if (is_main_heap) {
mi_assert_internal(subproc->heap_main == NULL);
subproc->heap_main = heap;
}
_mi_heap_init(heap, theap_slot, subproc, exclusive_arena_id);
return heap;
}
mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t exclusive_arena_id) {
// `mi_heap_new` may be the very first mimalloc call in a process, in which case the
// main heap does not exist yet and `_mi_heap_new_for_subproc` would allocate from a NULL `subproc->heap_main`.
mi_thread_init();
return _mi_heap_new_for_subproc(_mi_subproc(), exclusive_arena_id, false);
}
mi_heap_t* mi_heap_new(void) {
return mi_heap_new_in_arena(0);
}
// free all theaps belonging to this heap (without deleting their pages as we do this arena wise for efficiency)
static void mi_heap_free_theaps(mi_heap_t* heap) {
// This can run concurrently with a thread that terminates (see `init.c:mi_thread_theaps_done`),
// This can run concurrently with a thread that terminates (see `init.c:mi_thread_theaps_done`),
// and we need to ensure we free theaps atomically.
// We do this in a loop where we release the theaps_lock at every potential re-iteration to unblock
// potential concurrent thread termination which tries to remove the theap from our theaps list.
bool all_freed;
do {
all_freed = true;
mi_theap_t* theap = NULL;
mi_lock(&heap->theaps_lock) {
theap = heap->theaps;
while(theap != NULL) {
mi_theap_t* next = theap->hnext;
if (!_mi_theap_free(theap, false /* dont re-acquire the heap->theaps_lock */, true /* acquire the tld->theaps_lock though */ )) {
all_freed = false;
}
theap = next;
}
// We first detach our theaps list from any thread local lists
_mi_heap_detach_theaps(heap);
// Now we can safely free the theaps
mi_lock(&heap->theaps_lock) { // paranoia
mi_theap_t* theap = heap->theaps;
heap->theaps = NULL;
while(theap != NULL) {
mi_theap_t* next = theap->hnext;
theap->hnext = NULL;
theap->hprev = NULL;
mi_assert_internal(theap->tld==NULL);
// merge stats into the owning heap stats
_mi_stats_merge_into(&heap->stats, &theap->stats);
// and free
_mi_theap_decref(theap); // a cached entry can still point to the theap
theap = next;
}
if (!all_freed) {
mi_heap_stat_counter_increase(heap,heaps_delete_wait,1);
_mi_prim_thread_yield();
}
else {
mi_assert_internal(heap->theaps==NULL);
}
}
// set the theap thread local to NULL (so _mi_page_associated_theap does not read from a freed theap (through delete pages -> page_update_stats))
if (!_mi_is_process_heap_main(heap)) {
_mi_thread_local_free(heap->theap);
heap->theap = 0;
}
while(!all_freed);
}
// free the heap resources (assuming the pages are already moved/destroyed, and all theaps have been freed)
static void mi_heap_free(mi_heap_t* heap) {
mi_assert_internal(heap!=NULL && !_mi_is_heap_main(heap));
static void mi_heap_free(mi_heap_t* heap, bool acquire_heaps_lock) {
mi_assert_internal(heap!=NULL); // && !_mi_is_process_heap_main(heap));
// free all arena pages infos
mi_lock(&heap->arena_pages_lock) {
for (size_t i = 0; i < MI_MAX_ARENAS; i++) {
mi_arena_pages_t* arena_pages = mi_atomic_load_ptr_relaxed(mi_arena_pages_t, &heap->arena_pages[i]);
if (arena_pages!=NULL) {
mi_atomic_store_ptr_relaxed(mi_arena_pages_t, &heap->arena_pages[i], NULL);
mi_free(arena_pages);
const bool is_main = _mi_is_heap_main(heap);
if (!is_main) { // pages for the main heap are pre-allocated in the arenas
mi_lock(&heap->arena_pages_lock) {
for (size_t i = 0; i < MI_MAX_ARENAS; i++) {
mi_arena_pages_t* arena_pages = mi_atomic_load_ptr_relaxed(mi_arena_pages_t, &heap->arena_pages[i]);
if (arena_pages!=NULL) {
mi_atomic_store_ptr_relaxed(mi_arena_pages_t, &heap->arena_pages[i], NULL);
_mi_free_subproc_safe(arena_pages);
}
}
}
}
// remove the heap from the subproc
mi_heap_stats_merge_to_main(heap);
if (!is_main) {
mi_heap_stats_merge_to_main(heap);
}
else {
_mi_stats_merge_into(&heap->subproc->stats,&heap->stats);
}
mi_atomic_decrement_relaxed(&heap->subproc->heap_count);
mi_subproc_stat_decrease(heap->subproc, heaps, 1);
mi_lock(&heap->subproc->heaps_lock) {
mi_lock_maybe(&heap->subproc->heaps_lock, acquire_heaps_lock) {
if (heap->next!=NULL) { heap->next->prev = heap->prev; }
if (heap->prev!=NULL) { heap->prev->next = heap->next; }
else { heap->subproc->heaps = heap->next; }
}
_mi_thread_local_free(heap->theap);
mi_lock_done(&heap->theaps_lock);
mi_lock_done(&heap->os_abandoned_pages_lock);
mi_lock_done(&heap->arena_pages_lock);
mi_free(heap);
if (!_mi_is_process_heap_main(heap)) {
// _mi_thread_local_free(heap->theap);
_mi_free_subproc_safe(heap);
}
}
void mi_heap_delete(mi_heap_t* heap) {
if (heap==NULL) return;
if (_mi_is_heap_main(heap)) {
mi_heap_t* heap_main = mi_heap_get_heap_main(heap);
if (heap == heap_main) {
_mi_warning_message("cannot delete the main heap\n");
return;
}
mi_heap_free_theaps(heap);
_mi_heap_move_pages(heap, mi_heap_main());
mi_heap_free(heap);
_mi_heap_move_pages(heap, heap_main);
mi_heap_free(heap,true /* acquire subproc->heaps_lock */);
}
void _mi_heap_force_destroy(mi_heap_t* heap) {
void _mi_heap_force_destroy(mi_heap_t* heap, bool acquire_heaps_lock) {
if (heap==NULL) return;
mi_heap_free_theaps(heap);
_mi_heap_destroy_pages(heap);
if (!_mi_is_heap_main(heap)) { mi_heap_free(heap); } // todo: release locks of the main heap?
// if (_mi_subproc_main()->heap_main == heap) {
// _mi_stats_merge_into(&heap->subproc->stats,&heap->stats);
// }
// else
{
mi_heap_free(heap, acquire_heaps_lock); // todo: release locks of the main heap?
}
}
void mi_heap_destroy(mi_heap_t* heap) {
@@ -227,17 +264,18 @@ void mi_heap_destroy(mi_heap_t* heap) {
_mi_warning_message("cannot destroy the main heap\n");
return;
}
_mi_heap_force_destroy(heap);
_mi_heap_force_destroy(heap,true /* acquire subproc->heaps_lock */);
}
mi_heap_t* mi_heap_of(const void* p) {
mi_page_t* page = _mi_safe_ptr_page(p);
mi_page_t* const page = _mi_safe_ptr_page(p);
if (page==NULL) return NULL;
return mi_page_heap(page);
}
bool mi_any_heap_contains(const void* p) {
return (mi_heap_of(p)!=NULL);
mi_page_t* const page = _mi_safe_ptr_page(p);
return (page!=NULL);
}
bool mi_heap_contains(const mi_heap_t* heap, const void* p) {
@@ -256,8 +294,8 @@ bool mi_check_owned(const void* p) {
bool mi_unsafe_heap_page_is_under_utilized(mi_heap_t* heap, void* p, size_t perc_threshold) mi_attr_noexcept {
if (p==NULL) return false;
const mi_page_t* const page = _mi_safe_ptr_page(p); // Get the page containing this pointer
if (page==NULL || page->used==page->capacity || page->capacity < page->reserved) return false;
// If the page is the head of the queue, it is currently being used for
if (page==NULL || mi_page_used(page)==page->capacity || page->capacity < page->reserved) return false;
// If the page is the head of the queue, it is currently being used for
// allocations; we skip it to avoid immediate thrashing.
if (page->prev == NULL) return false;
@@ -265,9 +303,9 @@ bool mi_unsafe_heap_page_is_under_utilized(mi_heap_t* heap, void* p, size_t perc
const mi_heap_t* const page_heap = mi_page_heap(page);
if (page_heap==NULL) return false;
if (heap!=NULL && page_heap!=heap) return false;
// check utilization
if (page->capacity==0) return false;
if (perc_threshold>=100) return true;
return (perc_threshold >= ((100UL*page->used) / page->capacity));
return (perc_threshold >= ((100UL*mi_page_used(page)) / page->capacity));
}
File diff suppressed because it is too large. Load diff
+100 -31
View File
@@ -1,12 +1,12 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2024, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
// --------------------------------------------------------
// This module defines various std libc functions to reduce
// This module defines various standard libc functions to reduce
// the dependency on libc, and also prevent errors caused
// by some libc implementations when called before `main`
// executes (due to malloc redirection)
@@ -39,8 +39,8 @@ bool _mi_streq(const char* s, const char* t) {
return (*s == *t);
}
void _mi_strlcpy(char* dest, const char* src, size_t dest_size) {
if (dest==NULL || src==NULL || dest_size == 0) return;
bool _mi_strlcpy(char* dest, const char* src, size_t dest_size) {
if (dest==NULL || src==NULL || dest_size == 0) return (src==NULL || *src==0);
// copy until end of src, or when dest is (almost) full
while (*src != 0 && dest_size > 1) {
*dest++ = *src++;
@@ -48,23 +48,24 @@ void _mi_strlcpy(char* dest, const char* src, size_t dest_size) {
}
// always zero terminate
*dest = 0;
return (*src == 0);
}
void _mi_strlcat(char* dest, const char* src, size_t dest_size) {
if (dest==NULL || src==NULL || dest_size == 0) return;
bool _mi_strlcat(char* dest, const char* src, size_t dest_size) {
if (dest==NULL || src==NULL || dest_size == 0) return (src==NULL || *src==0);
// find end of string in the dest buffer
while (*dest != 0 && dest_size > 1) {
dest++;
dest_size--;
}
// and catenate
_mi_strlcpy(dest, src, dest_size);
return _mi_strlcpy(dest, src, dest_size);
}
size_t _mi_strnlen(const char* s, size_t max_len) {
if (s==NULL) return 0;
size_t len = 0;
while(s[len] != 0 && len < max_len) { len++; }
while(len < max_len && s[len] != 0) { len++; }
return len;
}
@@ -76,7 +77,7 @@ char* _mi_strnstr(char* s, size_t max_len, const char* pat) {
if (s==NULL) return NULL;
if (pat==NULL) return s;
const size_t m = _mi_strnlen(s, max_len);
const size_t n = _mi_strlen(pat);
const size_t n = _mi_strlen(pat);
for (size_t start = 0; start + n <= m; start++) {
size_t i = 0;
while (i<n && pat[i]==s[start+i]) {
@@ -88,16 +89,18 @@ char* _mi_strnstr(char* s, size_t max_len, const char* pat) {
}
#ifdef MI_NO_GETENV
bool _mi_getenv(const char* name, char* result, size_t result_size) {
int _mi_getenv(const char* name, char* result, size_t result_size) {
MI_UNUSED(name);
MI_UNUSED(result);
MI_UNUSED(result_size);
return false;
return ENOENT;
}
#else
bool _mi_getenv(const char* name, char* result, size_t result_size) {
if (name==NULL || result == NULL || result_size < 64) return false;
return _mi_prim_getenv(name,result,result_size);
int _mi_getenv(const char* name, char* result, size_t result_size) {
if (name==NULL || result == NULL || result_size < 64) return ENOENT;
// change the result of _mi_prim_getenv to an errno result
const int res = _mi_prim_getenv(name,result,result_size);
return (res > 0 ? 0 : (res == 0 ? ENOENT : EAGAIN));
}
#endif
@@ -116,14 +119,14 @@ bool _mi_atomic_once_enter(mi_atomic_once_t* once) {
}
const mi_threadid_t current_tid = _mi_thread_id();
if (once_tid == current_tid) {
return false; // recursive invocation; we need this for process_init for example
}
return false; // recursive invocation; don't block on ourselves
}
mi_lock_acquire(&once->lock);
uintptr_t expected = 0;
if (mi_atomic_cas_strong_acq_rel(&once->tid, &expected, current_tid)) { // could use atomic_load/store as well
return true; // should execute and release
}
}
else {
mi_lock_release(&once->lock);
return false; // already another thread entered and released
@@ -134,9 +137,73 @@ void _mi_atomic_once_release(mi_atomic_once_t* once) {
if (mi_atomic_load_acquire(&once->tid)>1) { // paranoia
mi_atomic_store_release(&once->tid,1); // done executing
mi_lock_release(&once->lock);
}
}
}
#if MI_USE_PTHREADS
mi_decl_noinline bool _mi_pthread_key_create(pthread_key_t* pkey, void (*destruct)(void*), void* init) {
int err = pthread_key_create(pkey,destruct);
if mi_unlikely(err!=0) {
*pkey = MI_PTHREAD_KEY_INVALID;
_mi_error_message(ENOMEM,"unable to allocate a thread local variable (error %d)\n", err);
return false;
}
mi_assert_internal(*pkey != MI_PTHREAD_KEY_INVALID);
if (init!=NULL) {
pthread_setspecific(*pkey,init);
};
mi_assert_internal(pthread_getspecific(*pkey)==init);
return true;
}
#endif
// --------------------------------------------------------
// Detect CPU features
// --------------------------------------------------------
mi_decl_cache_align bool _mi_cpu_has_popcnt = false;
#if (MI_ARCH_X64 || MI_ARCH_X86)
#if defined(__GNUC__)
// #include <cpuid.h>
static bool mi_cpuid(uint32_t* regs4, uint32_t level, uint32_t sublevel) {
// note: use explicit assembly instead of __get_cpuid as we need the sublevel (in ecx)
// (on Ubuntu 22 with WSL the __get_cpuid does not clear ecx for level 7 which is incorrect).
uint32_t eax, ebx, ecx, edx;
__asm __volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(level), "c"(sublevel) : );
regs4[0] = eax;
regs4[1] = ebx;
regs4[2] = ecx;
regs4[3] = edx;
return true;
}
#elif defined(_MSC_VER)
static bool mi_cpuid(uint32_t* regs4, uint32_t level, uint32_t sublevel) {
__cpuidex((int32_t*)regs4, (int32_t)level, (int32_t)sublevel);
return true;
}
#else
static bool mi_cpuid(uint32_t* regs4, uint32_t level, uint32_t sublevel) {
MI_UNUSED(regs4); MI_UNUSED(level); MI_UNUSED(sublevel);
return false;
}
#endif
void _mi_detect_cpu_features(void) {
uint32_t cpu_info[4];
if (mi_cpuid(cpu_info, 1, 0)) {
_mi_cpu_has_popcnt = ((cpu_info[2] & (1 << 23)) != 0); // bit 23 of ECX : see <https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits>
}
}
#else
void _mi_detect_cpu_features(void) {
#if MI_ARCH_ARM64 || defined(__riscv_zbb) || defined(__riscv_b)
_mi_cpu_has_popcnt = true;
#endif
}
#endif
// --------------------------------------------------------
// Define our own limited `_mi_vsnprintf` and `_mi_snprintf`
@@ -196,22 +263,20 @@ static void mi_out_num(uintmax_t x, size_t base, char prefix, char** out, char*
mi_outc('0',out,end);
}
else {
// output digits in reverse
char* start = *out;
while (x > 0) {
#define MI_MAX_OUT_DIGITS (160) /* a 512 bit number has 155 digits */
char num[MI_MAX_OUT_DIGITS];
int dcount = 0;
while(x>0 && dcount < MI_MAX_OUT_DIGITS) {
char digit = (char)(x % base);
mi_outc((digit <= 9 ? '0' + digit : 'A' + digit - 10),out,end);
num[dcount++] = (digit <= 9 ? '0' + digit : 'A' + digit - 10);
x = x / base;
}
if (dcount>=MI_MAX_OUT_DIGITS) return; // don't output anything?
if (prefix != 0) {
mi_outc(prefix, out, end);
}
size_t len = *out - start;
// and reverse in-place
for (size_t i = 0; i < (len / 2); i++) {
char c = start[len - i - 1];
start[len - i - 1] = start[i];
start[i] = c;
while(dcount-- > 0) {
mi_outc(num[dcount], out, end);
}
}
}
@@ -258,7 +323,10 @@ int _mi_vsnprintf(char* buf, size_t bufsize, const char* fmt, va_list args) {
if (c >= '1' && c <= '9') {
width = (c - '0'); MI_NEXTC();
while (c >= '0' && c <= '9') {
width = (10 * width) + (c - '0'); MI_NEXTC();
if (width < SIZE_MAX/1024) { // no overflow
width = (10 * width) + (c - '0');
}
MI_NEXTC();
}
if (c == 0) break; // extra check due to while
}
@@ -297,7 +365,7 @@ int _mi_vsnprintf(char* buf, size_t bufsize, const char* fmt, va_list args) {
if (width == 0 && (c == 'x' || c == 'p')) {
if (c == 'p') { width = 2 * (x <= UINT32_MAX ? 4 : ((x >> 16) <= UINT32_MAX ? 6 : sizeof(void*))); }
if (width == 0) { width = 2; }
fill = '0';
if (alignright) { fill = '0'; }
}
mi_out_num(x, (c == 'x' || c == 'p' ? 16 : 10), numplus, &out, end);
}
@@ -350,6 +418,7 @@ int _mi_snprintf(char* buf, size_t buflen, const char* fmt, ...) {
return written;
}
#undef MI_NEXTC
// --------------------------------------------------------
@@ -365,7 +434,7 @@ static size_t mi_ctz_generic32(uint32_t x) {
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
if (x==0) return 32;
return debruijn[(uint32_t)((x & -(int32_t)x) * (uint32_t)(0x077CB531U)) >> 27];
return debruijn[(uint32_t)((x & (~x + 1U)) * (uint32_t)(0x077CB531U)) >> 27];
}
static size_t mi_clz_generic32(uint32_t x) {
+88 -88
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -17,7 +17,7 @@ static long mi_max_warning_count = 16; // stop outputting warnings after this (u
static void mi_add_stderr_output(void);
int mi_version(void) mi_attr_noexcept {
int mi_version(void) {
return MI_MALLOC_VERSION;
}
@@ -49,7 +49,7 @@ int mi_version(void) mi_attr_noexcept {
// in KiB
#ifndef MI_DEFAULT_ARENA_RESERVE
#if (MI_INTPTR_SIZE>4)
#if (MI_SIZE_SIZE>4)
#define MI_DEFAULT_ARENA_RESERVE 1024L*1024L
#else
#define MI_DEFAULT_ARENA_RESERVE 128L*1024L
@@ -77,15 +77,15 @@ int mi_version(void) mi_attr_noexcept {
#endif
#ifndef MI_DEFAULT_GUARDED_SAMPLE_RATE
#if MI_GUARDED && !MI_DEBUG
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 4000
#if MI_GUARDED && MI_DEBUG
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0 /* MI_MiB */
#else
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0
#endif
#endif
#ifndef MI_DEFAULT_PAGEMAP_COMMIT
#if defined(__APPLE__) // when overloading malloc, we still get mixed pointers sometimes on macOS; this avoids a bad access
#if defined(__APPLE__) && MI_PAGE_MAP_FLAT // when overloading malloc, we still get mixed pointers sometimes on macOS; this avoids a bad access
#define MI_DEFAULT_PAGEMAP_COMMIT 1
#else
#define MI_DEFAULT_PAGEMAP_COMMIT 0
@@ -104,10 +104,15 @@ int mi_version(void) mi_attr_noexcept {
#if defined(__ANDROID__)
#define MI_DEFAULT_ALLOW_THP 0
#else
#define MI_DEFAULT_ALLOW_THP 1
// #define MI_DEFAULT_ALLOW_THP 1 // allow THP but purging may split up THP pages
#define MI_DEFAULT_ALLOW_THP 2 // allow THP and set the minimal purge size to 2MiB to avoid breaking them up
#endif
#endif
#ifndef MI_DEFAULT_COLLECT_MERGES_STATS
#define MI_DEFAULT_COLLECT_MERGES_STATS 1
#endif
// Static options
static mi_option_desc_t mi_options[_mi_option_last] =
{
@@ -121,7 +126,7 @@ static mi_option_desc_t mi_options[_mi_option_last] =
{ MI_DEFAULT_VERBOSE, MI_OPTION_UNINIT, MI_OPTION(verbose) },
// some of the following options are experimental and not all combinations are allowed.
{ 1, MI_OPTION_UNINIT, MI_OPTION(deprecated_eager_commit) },
{ 1, MI_OPTION_UNINIT, MI_OPTION(deprecated_eager_commit) },
{ MI_DEFAULT_ARENA_EAGER_COMMIT,
MI_OPTION_UNINIT, MI_OPTION_LEGACY(arena_eager_commit,eager_region_commit) }, // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux)
{ 1, MI_OPTION_UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) }, // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)
@@ -134,9 +139,9 @@ static mi_option_desc_t mi_options[_mi_option_last] =
MI_OPTION_UNINIT, MI_OPTION(reserve_os_memory) }, // reserve N KiB OS memory in advance (use `option_get_size`)
{ 0, MI_OPTION_UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
{ 0, MI_OPTION_UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free
{ 0, MI_OPTION_UNINIT, MI_OPTION(deprecated_abandoned_page_purge) },
{ 0, MI_OPTION_UNINIT, MI_OPTION(deprecated_abandoned_page_purge) },
{ 0, MI_OPTION_UNINIT, MI_OPTION(deprecated_segment_reset) }, // reset segment memory on free (needs eager commit)
{ 1, MI_OPTION_UNINIT, MI_OPTION(deprecated_eager_commit_delay) },
{ 1, MI_OPTION_UNINIT, MI_OPTION(deprecated_eager_commit_delay) },
{ 1000,MI_OPTION_UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds
{ 0, MI_OPTION_UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
{ 0, MI_OPTION_UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
@@ -146,15 +151,11 @@ static mi_option_desc_t mi_options[_mi_option_last] =
{ 10, MI_OPTION_UNINIT, MI_OPTION(deprecated_max_segment_reclaim)}, // max. percentage of the abandoned segments to be reclaimed per try.
{ 0, MI_OPTION_UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
{ MI_DEFAULT_ARENA_RESERVE, MI_OPTION_UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)
{ 1, MI_OPTION_UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
{ 4, MI_OPTION_UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
{ 1, MI_OPTION_UNINIT, MI_OPTION_LEGACY(deprecated_purge_extend_delay, decommit_extend_delay) },
{ MI_DEFAULT_DISALLOW_ARENA_ALLOC, MI_OPTION_UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
{ 400, MI_OPTION_UNINIT, MI_OPTION(retry_on_oom) }, // windows only: retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries.
#if defined(MI_VISIT_ABANDONED)
{ 1, MI_OPTION_INITIALIZED, MI_OPTION(visit_abandoned) }, // allow visiting theap blocks in abandoned segments; requires taking locks during reclaim.
#else
{ 0, MI_OPTION_UNINIT, MI_OPTION(visit_abandoned) },
#endif
{ 1, MI_OPTION_UNINIT, MI_OPTION(deprecated_visit_abandoned) },
{ 0, MI_OPTION_UNINIT, MI_OPTION(guarded_min) }, // only used when building with MI_GUARDED: minimal rounded object size for guarded objects
{ MI_GiB, MI_OPTION_UNINIT, MI_OPTION(guarded_max) }, // only used when building with MI_GUARDED: maximal rounded object size for guarded objects
{ 0, MI_OPTION_UNINIT, MI_OPTION(guarded_precise) }, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
@@ -175,16 +176,18 @@ static mi_option_desc_t mi_options[_mi_option_last] =
MI_OPTION_UNINIT, MI_OPTION(page_cross_thread_max_reclaim) }, // don't reclaim (small) pages across threads if we already own N pages in that size class
{ MI_DEFAULT_ALLOW_THP,
MI_OPTION_UNINIT, MI_OPTION(allow_thp) }, // allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process.
{ 0, MI_OPTION_UNINIT, MI_OPTION(minimal_purge_size) }, // set minimal purge size (in KiB) (=0). Using 0 resolves to either 64 (or 2048 if `mi_option_allow_thp==2`).
{ MI_DEFAULT_ARENA_MAX_OBJECT_SIZE,
MI_OPTION_UNINIT, MI_OPTION(arena_max_object_size) }, // set maximal object size that can be allocated in an arena (in KiB) (=2GiB on 64-bit).
{ 0, MI_OPTION_UNINIT, MI_OPTION(minimal_purge_size) }, // set minimal purge size (in KiB) (=0). Using 0 resolves to either 64 (or 2048 if THP is enabled).
{ MI_DEFAULT_ARENA_MAX_OBJECT_SIZE,
MI_OPTION_UNINIT, MI_OPTION(arena_max_object_size) }, // set maximal object size that can be allocated in an arena (in KiB) (=2GiB on 64-bit).
{ 0, MI_OPTION_UNINIT, MI_OPTION(arena_is_numa_local) }, // associate local numa node with an initial arena allocation
{ MI_DEFAULT_COLLECT_MERGES_STATS,
MI_OPTION_UNINIT, MI_OPTION(collect_merges_stats) }, // on each theap collect, stats are merged with the parent heap
};
static void mi_option_init(mi_option_desc_t* desc);
static bool mi_option_has_size_in_kib(mi_option_t option) {
return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve ||
return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve ||
option == mi_option_minimal_purge_size || option == mi_option_arena_max_object_size);
}
@@ -203,7 +206,7 @@ void _mi_options_init(void) {
_mi_warning_message("option 'allow_large_os_pages' is disabled to allow for guarded objects\n");
}
}
#endif
#endif
}
// called at actual process load, it should be safe to print now
@@ -221,15 +224,6 @@ mi_decl_export void mi_options_print_out(mi_output_fun* out, void* arg) mi_attr_
const int vermajor = MI_MALLOC_VERSION/10000;
const int verminor = (MI_MALLOC_VERSION%10000)/100;
const int verpatch = (MI_MALLOC_VERSION%100);
/* CBM LOCAL PATCH (keep across mimalloc refreshes): upstream appends
* "(built on __DATE__, __TIME__)" here. mimalloc is linked into every cbm
* binary, so those two macros made every build byte-unique — two compilations
* of identical source seconds apart could never produce the same hash. That
* defeats reproducible builds, and it also means a released artifact can never
* inherit an antivirus false-positive determination made about its
* predecessor, because every rebuild is a brand-new file to a reputation
* system. The banner keeps the version; the wall clock is not diagnostics.
* -Wdate-time in CFLAGS_COMMON makes any new use a build failure. */
_mi_fprintf(out, arg, "v%i.%i.%i%s%s\n", vermajor, verminor, verpatch,
#if defined(MI_CMAKE_BUILD_TYPE)
", " mi_stringify(MI_CMAKE_BUILD_TYPE)
@@ -252,16 +246,34 @@ mi_decl_export void mi_options_print_out(mi_output_fun* out, void* arg) mi_attr_
_mi_fprintf(out, arg, "option '%s': %ld %s\n", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? "KiB" : ""));
}
// show build configuration
_mi_fprintf(out, arg, "debug level : %d\n", MI_DEBUG );
_mi_fprintf(out, arg, "secure level: %d\n", MI_SECURE );
_mi_fprintf(out, arg, "mem tracking: %s\n", MI_TRACK_TOOL);
// show build configuration
#if MI_PAGE_META_IS_ALIGNED && MI_PAGE_META_SMALL_IS_ALIGNED
_mi_fprintf(out, arg, "free mode : (small) aligned, page size: %zu\n", sizeof(mi_page_t));
#elif MI_PAGE_META_IS_ALIGNED
_mi_fprintf(out, arg, "free mode : aligned, page size: %zu\n", sizeof(mi_page_t));
#elif MI_PAGE_META_SMALL_IS_ALIGNED
_mi_fprintf(out, arg, "free mode : small aligned + pagemap, page size: %zu\n", sizeof(mi_page_t));
#elif MI_FREE_IS_CHECKED
_mi_fprintf(out, arg, "free mode : checked, page size: %zu\n", sizeof(mi_page_t));
#else
_mi_fprintf(out, arg, "free mode : pagemap, page size: %zu\n", sizeof(mi_page_t));
#endif
#if MI_ENCODE_FREELIST
_mi_fprintf(out, arg, "free lists : encoded with %d key(s)\n", MI_PAGE_KEY_COUNT);
#endif
#if MI_GUARDED
_mi_fprintf(out, arg, "guarded build: %s\n", mi_option_get(mi_option_guarded_sample_rate) != 0 ? "enabled" : "disabled");
_mi_fprintf(out, arg, "guarded mode: %s, rate=%ld\n", MI_SAMPLE==2 ? "fine-grained" : "enabled", mi_option_get(mi_option_guarded_sample_rate));
#endif
#if MI_PROFILE
_mi_fprintf(out, arg, "profiling : %s\n", MI_SAMPLE==2 ? "fine-grained" : "enabled");
#endif
#if MI_TSAN
_mi_fprintf(out, arg, "thread santizer enabled\n");
#endif
_mi_fprintf(out, arg, "mem tracking: %s\n", MI_TRACK_TOOL);
_mi_fprintf(out, arg, "debug level : %d\n", MI_DEBUG );
_mi_fprintf(out, arg, "secure level: %d\n", MI_SECURE );
_mi_fprintf(out, arg, "padding : %s\n", (MI_PADDING==0 ? "none" : (MI_PADDING==1 ? "enabled" : "byte precise")));
}
mi_decl_export void mi_options_print(void) mi_attr_noexcept {
@@ -446,25 +458,12 @@ static void mi_add_stderr_output(void) {
static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors
static _Atomic(size_t) warning_count; // = 0; // when >= max_warning_count stop emitting warnings
// When overriding malloc, we may recurse into mi_vfprintf if an allocation
// inside the C runtime causes another message.
// In some cases (like on macOS) the loader already allocates which
// calls into mimalloc; if we then access thread locals (like `recurse`)
// this may crash as the access may call _tlv_bootstrap that tries to
// (recursively) invoke malloc again to allocate space for the thread local
// variables on demand. This is why we use a _mi_preloading test on such
// platforms. However, C code generator may move the initial thread local address
// load before the `if` and we therefore split it out in a separate function.
static mi_decl_thread bool recurse = false;
static mi_decl_noinline bool mi_recurse_enter_prim(void) {
if (recurse) return false;
recurse = true;
return true;
}
static mi_decl_noinline void mi_recurse_exit_prim(void) {
recurse = false;
/* nothing */
}
static bool mi_recurse_enter(void) {
@@ -482,7 +481,7 @@ static void mi_recurse_exit(void) {
}
void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {
if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr?
if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // todo: use mi_out_stderr for stderr?
if (!mi_recurse_enter()) return;
out = mi_out_get_default(&arg);
if (prefix != NULL) out(prefix, arg);
@@ -514,7 +513,7 @@ void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {
}
static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {
if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {
if (prefix != NULL && _mi_strnlen(prefix,33) <= 32) { // && !_mi_is_main_thread()) {
char tprefix[64];
_mi_snprintf(tprefix, sizeof(tprefix), "%sthread 0x%tx: ", prefix, (uintptr_t)_mi_thread_id());
mi_vfprintf(out, arg, tprefix, fmt, args);
@@ -531,13 +530,6 @@ void _mi_raw_message(const char* fmt, ...) {
va_end(args);
}
void _mi_message(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
va_end(args);
}
void _mi_trace_message(const char* fmt, ...) {
if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher
va_list args;
@@ -590,24 +582,27 @@ static _Atomic(void*) mi_error_arg; // = NULL
static void mi_error_default(int err) {
MI_UNUSED(err);
#if (MI_DEBUG>0)
if (err==EFAULT) {
#ifdef _MSC_VER
__debugbreak();
#endif
abort();
#if (MI_DEBUG>0)
if (err==EFAULT) {
#ifdef _MSC_VER
__debugbreak();
#endif
abort();
}
#endif
#if (MI_SECURE>0)
if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
abort();
}
#endif
#if defined(MI_XMALLOC)
if (err==ENOMEM || err==EOVERFLOW || err==EINVAL) { // abort on memory allocation fails in xmalloc mode
abort();
}
#endif
if (errno==0) {
errno = (err==EINVAL ? EINVAL : ENOMEM /* compatibility */ );
}
#endif
#if (MI_SECURE>0)
if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
abort();
}
#endif
#if defined(MI_XMALLOC)
if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode
abort();
}
#endif
}
void mi_register_error(mi_error_fun* fun, void* arg) {
@@ -621,7 +616,7 @@ void _mi_error_message(int err, const char* fmt, ...) {
va_start(args, fmt);
mi_show_error_message(fmt, args);
va_end(args);
// and call the error handler which may abort (or return normally)
// and call the error handler which may abort (or return normally, potentially setting errno)
if (mi_error_handler != NULL) {
mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));
}
@@ -630,14 +625,17 @@ void _mi_error_message(int err, const char* fmt, ...) {
}
}
mi_decl_noinline mi_block_t* _mi_block_next_is_corrupted(const mi_page_t* page, const mi_block_t* block, const mi_block_t* next) {
_mi_error_message(EFAULT, "corrupted free list entry of size %zub at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next);
return NULL;
}
// --------------------------------------------------------
// Initialize options by checking the environment
// --------------------------------------------------------
// TODO: implement ourselves to reduce dependencies on the C runtime
#include <stdlib.h> // strtol
#include <string.h> // strstr
static void mi_option_init(mi_option_desc_t* desc) {
// Read option value from the environment
@@ -645,17 +643,17 @@ static void mi_option_init(mi_option_desc_t* desc) {
char buf[64+1];
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
_mi_strlcat(buf, desc->name, sizeof(buf));
bool found = _mi_getenv(buf, s, sizeof(s));
if (!found && desc->legacy_name != NULL) {
int err = _mi_getenv(buf, s, sizeof(s));
if (err==ENOENT && desc->legacy_name != NULL) {
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
_mi_strlcat(buf, desc->legacy_name, sizeof(buf));
found = _mi_getenv(buf, s, sizeof(s));
if (found) {
err = _mi_getenv(buf, s, sizeof(s));
if (err==0) {
_mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name);
}
}
if (found) {
if (err==0) {
size_t len = _mi_strnlen(s, sizeof(buf) - 1);
for (size_t i = 0; i < len; i++) {
buf[i] = _mi_toupper(s[i]);
@@ -665,14 +663,15 @@ static void mi_option_init(mi_option_desc_t* desc) {
desc->value = 1;
desc->init = MI_OPTION_INITIALIZED;
}
else if (_mi_streq(buf,"0") || _mi_streq(buf,"FALSE") || _mi_streq(buf,"NO") || _mi_streq(buf,"OFF")) {
else if (_mi_streq(buf,"0") || _mi_streq(buf,"FALSE") || _mi_streq(buf,"NO") || _mi_streq(buf,"OFF")) {
desc->value = 0;
desc->init = MI_OPTION_INITIALIZED;
}
else {
char* end = buf;
errno = 0;
long value = strtol(buf, &end, 10);
if (mi_option_has_size_in_kib(desc->option)) {
if (errno==0 && mi_option_has_size_in_kib(desc->option)) {
// this option is interpreted in KiB to prevent overflow of `long` for large allocations
// (long is 32-bit on 64-bit windows, which allows for 4TiB max.)
size_t size = (value < 0 ? 0 : (size_t)value);
@@ -687,7 +686,7 @@ static void mi_option_init(mi_option_desc_t* desc) {
if (overflow || size > (MI_MAX_ALLOC_SIZE / MI_KiB)) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }
value = (size > LONG_MAX ? LONG_MAX : (long)size);
}
if (*end == 0) {
if (errno==0 && *end == 0) {
mi_option_set(desc->option, value);
}
else {
@@ -707,7 +706,8 @@ static void mi_option_init(mi_option_desc_t* desc) {
}
mi_assert_internal(desc->init != MI_OPTION_UNINIT);
}
else if (!_mi_preloading()) {
else if (err==ENOENT) {
desc->init = MI_OPTION_DEFAULTED;
}
// and on another error, keep unitialized to try again (can happen during preloading if getenv is not available)
}
+135 -105
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -8,12 +8,13 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc/internal.h"
#include "mimalloc/atomic.h"
#include "mimalloc/prim.h"
#include "mimalloc/prim-tls.h" // _mi_theap_default for random
/* -----------------------------------------------------------
Initialization.
----------------------------------------------------------- */
#ifndef MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB
#if MI_INTPTR_SIZE < 8
#if MI_SIZE_SIZE < 8
#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB 4*MI_MiB // 4 GiB
#else
#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB 32*MI_MiB // 32 GiB
@@ -40,6 +41,9 @@ bool _mi_os_has_virtual_reserve(void) {
return mi_os_mem_config.has_virtual_reserve;
}
bool _mi_os_canuse_thp(void) {
return mi_os_mem_config.has_transparent_huge_pages;
}
// OS (small) page size
size_t _mi_os_page_size(void) {
@@ -54,13 +58,19 @@ size_t _mi_os_large_page_size(void) {
// minimal purge size. Can be larger than the page size if transparent huge pages are enabled.
size_t _mi_os_minimal_purge_size(void) {
size_t minsize = mi_option_get_size(mi_option_minimal_purge_size);
if (minsize != 0) {
if (minsize != 0) {
// set by user
return _mi_align_up(minsize, _mi_os_page_size());
}
else if (mi_os_mem_config.has_transparent_huge_pages && mi_option_get(mi_option_allow_thp) == 2) {
// don't break up THP pages;
// we don't do this by default as that can lead to increased memory usage (see issue #1282).
// on the other hand, breaking up THP pages can lead to performance degradation and perhaps
// allow_thp should be 2 by default (and the user can set the OS THP setting to [never]).
return _mi_os_large_page_size();
}
else {
// OS page size by default
return _mi_os_page_size();
}
}
@@ -103,53 +113,57 @@ void _mi_os_init(void) {
/* -----------------------------------------------------------
Util
-------------------------------------------------------------- */
bool _mi_os_decommit(void* addr, size_t size);
bool _mi_os_commit(void* addr, size_t size, bool* is_zero);
bool _mi_os_decommit(mi_subproc_t* subproc, void* addr, size_t size);
bool _mi_os_commit(mi_subproc_t* subproc, void* addr, size_t size, bool* is_zero);
// On systems with enough virtual address bits, we can do efficient aligned allocation by using
// the 2TiB to 30TiB area to allocate those. If we have at least 46 bits of virtual address
// space (64TiB) we use this technique. (but see issue #939)
#if (MI_INTPTR_SIZE >= 8) && !defined(MI_NO_ALIGNED_HINT) // && !defined(WIN32) && !defined(ANDROID)
#if (MI_SIZE_SIZE >= 8) && !defined(MI_NO_ALIGNED_HINT) // && !defined(WIN32) && !defined(ANDROID)
// Return a MI_HINT_ALIGN (4MiB) aligned address that is probably available.
// Return a `try_alignment` aligned address that is probably available.
// If this returns NULL, the OS will determine the address but on some OS's that may not be
// properly aligned which can be more costly as it needs to be adjusted afterwards.
// For a size > 16GiB this always returns NULL in order to guarantee good ASLR randomization;
// In secure mode, for a size > 16GiB this always returns NULL in order to guarantee good ASLR randomization;
// (otherwise an initial large allocation of say 2TiB has a 50% chance to include (known) addresses
// in the middle of the 2TiB - 6TiB address range (see issue #372))
#define MI_HINT_ALIGN ((uintptr_t)4 << 20) // 4MiB alignment
#define MI_HINT_BASE ((uintptr_t)2 << 40) // 2TiB start
#define MI_HINT_AREA ((uintptr_t)4 << 40) // upto (2+4) 6TiB (since before win8 there is "only" 8TiB available to processes)
#define MI_HINT_MAX ((uintptr_t)30 << 40) // wrap after 30TiB (area after 32TiB is used for huge OS pages)
void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size)
void* _mi_os_get_aligned_hint(size_t try_alignment, size_t sze)
{
static mi_decl_cache_align _Atomic(uintptr_t) aligned_base; // = 0
// todo: perhaps only do alignment hints if THP is enabled?
if (try_alignment <= mi_os_mem_config.alloc_granularity || try_alignment > MI_HINT_ALIGN) return NULL;
if (try_alignment <= mi_os_mem_config.alloc_granularity || try_alignment > 16*MI_GiB) return NULL;
if (mi_os_mem_config.virtual_address_bits < 46) return NULL; // < 64TiB virtual address space
size = _mi_align_up(size, MI_HINT_ALIGN);
if (size > 16*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at least 1/(MI_HINT_AREA / 1<<34)
size += MI_HINT_ALIGN; // put in virtual gaps between hinted blocks; this splits VLA's but increases guarded areas.
uintptr_t hint = mi_atomic_add_acq_rel(&aligned_base, size);
size_t req_size = sze + _mi_os_page_size(); // always reserve a bit more to create virtual gaps between hinted blocks.
req_size += (try_alignment - 1); // ensure we can align in the requested size
req_size = _mi_align_up(req_size, _mi_os_large_page_size());
#if (MI_SECURE>=1)
if (req_size > 32*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<34) = 1/256
#endif
uintptr_t hint = mi_atomic_add_acq_rel(&aligned_base, req_size);
if (hint == 0 || hint > MI_HINT_MAX) { // wrap or initialize
uintptr_t init = MI_HINT_BASE;
#if (MI_SECURE>=1 || defined(NDEBUG)) // security: randomize start of aligned allocations unless in debug mode
#if (MI_SECURE>=1 || !MI_DEBUG) // security: randomize start of aligned allocations unless in debug mode
mi_theap_t* const theap = _mi_theap_default(); // don't use `mi_theap_get_default()` as that can cause allocation recursively (issue #1267)
if (!mi_theap_is_initialized(theap)) return NULL; // no hint as we lack randomness at this point
const uintptr_t r = _mi_theap_random_next(theap);
init = init + ((MI_HINT_ALIGN * ((r>>17) & 0xFFFFF)) % MI_HINT_AREA); // (randomly 20 bits)*4MiB == 0 to 4TiB
init = init + ((MI_MiB * ((r>>17) & 0x3FFFFF)) % MI_HINT_AREA); // (randomly 22 bits)* 1MiB == 0 to 4TiB
#endif
uintptr_t expected = hint + size;
uintptr_t expected = hint + req_size;
mi_atomic_cas_strong_acq_rel(&aligned_base, &expected, init);
hint = mi_atomic_add_acq_rel(&aligned_base, size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all
hint = mi_atomic_add_acq_rel(&aligned_base, req_size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all
if (hint==0) return NULL;
}
mi_assert_internal(hint%MI_HINT_ALIGN == 0);
if (hint%try_alignment != 0) return NULL;
return (void*)hint;
const uintptr_t hint_align = _mi_align_up(hint,try_alignment);
mi_assert_internal(hint_align + sze < hint + req_size);
return (void*)hint_align;
}
#else
void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size) {
@@ -173,7 +187,7 @@ size_t _mi_os_secure_guard_page_size(void) {
}
// In secure mode, try to decommit an area and output a warning if this fails.
bool _mi_os_secure_guard_page_set_at(void* addr, mi_memid_t memid) {
bool _mi_os_secure_guard_page_set_at(mi_subproc_t* subproc, void* addr, mi_memid_t memid) {
if (addr == NULL) return true;
#if MI_SECURE > 0
bool ok = false;
@@ -183,7 +197,7 @@ bool _mi_os_secure_guard_page_set_at(void* addr, mi_memid_t memid) {
ok = (*(arena->commit_fun))(false /* decommit */, addr, _mi_os_secure_guard_page_size(), NULL, arena->commit_fun_arg);
}
else {
ok = _mi_os_decommit(addr, _mi_os_secure_guard_page_size());
ok = _mi_os_decommit(subproc, addr, _mi_os_secure_guard_page_size());
}
}
if (!ok) {
@@ -191,18 +205,18 @@ bool _mi_os_secure_guard_page_set_at(void* addr, mi_memid_t memid) {
}
return ok;
#else
MI_UNUSED(memid);
MI_UNUSED(subproc); MI_UNUSED(memid);
return true;
#endif
}
// In secure mode, try to decommit an area and output a warning if this fails.
bool _mi_os_secure_guard_page_set_before(void* addr, mi_memid_t memid) {
return _mi_os_secure_guard_page_set_at((uint8_t*)addr - _mi_os_secure_guard_page_size(), memid);
bool _mi_os_secure_guard_page_set_before(mi_subproc_t* subproc, void* addr, mi_memid_t memid) {
return _mi_os_secure_guard_page_set_at(subproc, (uint8_t*)addr - _mi_os_secure_guard_page_size(), memid);
}
// In secure mode, try to recommit an area
bool _mi_os_secure_guard_page_reset_at(void* addr, mi_memid_t memid) {
bool _mi_os_secure_guard_page_reset_at(mi_subproc_t* subproc, void* addr, mi_memid_t memid) {
if (addr == NULL) return true;
#if MI_SECURE > 0
if (!memid.is_pinned) {
@@ -211,18 +225,18 @@ bool _mi_os_secure_guard_page_reset_at(void* addr, mi_memid_t memid) {
return (*(arena->commit_fun))(true, addr, _mi_os_secure_guard_page_size(), NULL, arena->commit_fun_arg);
}
else {
return _mi_os_commit(addr, _mi_os_secure_guard_page_size(), NULL);
return _mi_os_commit(subproc, addr, _mi_os_secure_guard_page_size(), NULL);
}
}
#else
MI_UNUSED(memid);
MI_UNUSED(subproc); MI_UNUSED(memid);
#endif
return true;
}
// In secure mode, try to recommit an area
bool _mi_os_secure_guard_page_reset_before(void* addr, mi_memid_t memid) {
return _mi_os_secure_guard_page_reset_at((uint8_t*)addr - _mi_os_secure_guard_page_size(), memid);
bool _mi_os_secure_guard_page_reset_before(mi_subproc_t* subproc, void* addr, mi_memid_t memid) {
return _mi_os_secure_guard_page_reset_at(subproc, (uint8_t*)addr - _mi_os_secure_guard_page_size(), memid);
}
@@ -230,23 +244,27 @@ bool _mi_os_secure_guard_page_reset_before(void* addr, mi_memid_t memid) {
Free memory
-------------------------------------------------------------- */
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_subproc_t* subproc);
static void mi_os_free_huge_os_pages(mi_subproc_t* subproc, void* p, size_t size);
static void mi_os_prim_free(void* addr, size_t size, size_t commit_size, mi_subproc_t* subproc) {
static void mi_os_prim_free(mi_subproc_t* subproc, void* addr, size_t size, size_t commit_size, bool adjust) {
mi_assert_internal(subproc!=NULL);
mi_assert_internal((size % _mi_os_page_size()) == 0);
if (addr == NULL) return; // || _mi_os_is_huge_reserved(addr)
int err = _mi_prim_free(addr, size); // allow size==0 (issue #1041)
if (err != 0) {
_mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr);
}
if (subproc == NULL) { subproc = _mi_subproc(); } // from `mi_arenas_unsafe_destroy` we pass subproc_main explicitly as we can no longer use the theap pointer
if (commit_size > 0) {
mi_subproc_stat_decrease(subproc, committed, commit_size);
if (adjust) {
if (commit_size>0) { mi_subproc_stat_adjust_decrease(subproc, committed, commit_size); }
mi_subproc_stat_adjust_decrease(subproc, reserved, size);
}
else {
if (commit_size>0) { mi_subproc_stat_decrease(subproc, committed, commit_size); }
mi_subproc_stat_decrease(subproc, reserved, size);
}
mi_subproc_stat_decrease(subproc, reserved, size);
}
void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid, mi_subproc_t* subproc /* can be NULL */) {
void _mi_os_free_ex(mi_subproc_t* subproc, void* addr, size_t size, bool still_committed, mi_memid_t memid) {
if (mi_memkind_is_os(memid.memkind)) {
size_t csize = memid.mem.os.size;
if (csize==0) { csize = _mi_os_good_alloc_size(size); }
@@ -268,10 +286,10 @@ void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t me
// free it
if (memid.memkind == MI_MEM_OS_HUGE) {
mi_assert(memid.is_pinned);
mi_os_free_huge_os_pages(base, csize, subproc);
mi_os_free_huge_os_pages(subproc, base, csize);
}
else {
mi_os_prim_free(base, csize, (still_committed ? commit_size : 0), subproc);
mi_os_prim_free(subproc, base, csize, (still_committed ? commit_size : 0), false);
}
}
else {
@@ -280,8 +298,8 @@ void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t me
}
}
void _mi_os_free(void* p, size_t size, mi_memid_t memid) {
_mi_os_free_ex(p, size, true, memid, NULL);
void _mi_os_free(mi_subproc_t* subproc, void* p, size_t size, mi_memid_t memid) {
_mi_os_free_ex(subproc, p, size, true, memid);
}
@@ -291,7 +309,7 @@ void _mi_os_free(void* p, size_t size, mi_memid_t memid) {
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
// Also `hint_addr` is a hint and may be ignored.
static void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
static void* mi_os_prim_alloc_at(mi_subproc_t* subproc, void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
mi_assert_internal(is_zero != NULL);
mi_assert_internal(is_large != NULL);
@@ -312,11 +330,11 @@ static void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignm
_mi_warning_message("unable to allocate OS memory (error: %d (0x%x), addr: %p, size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, hint_addr, size, try_alignment, commit, allow_large);
}
mi_os_stat_counter_increase(mmap_calls, 1);
mi_subproc_stat_counter_increase(subproc, mmap_calls, 1);
if (p != NULL) {
mi_os_stat_increase(reserved, size);
mi_subproc_stat_increase(subproc, reserved, size);
if (commit) {
mi_os_stat_increase(committed, size);
mi_subproc_stat_increase(subproc, committed, size);
// seems needed for asan (or `mimalloc-test-api` fails)
#ifdef MI_TRACK_ASAN
if (*is_zero) { mi_track_mem_defined(p,size); }
@@ -327,14 +345,14 @@ static void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignm
return p;
}
static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
return mi_os_prim_alloc_at(NULL, size, try_alignment, commit, allow_large, is_large, is_zero);
static void* mi_os_prim_alloc(mi_subproc_t* subproc, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
return mi_os_prim_alloc_at(subproc, NULL /* hint addr */, size, try_alignment, commit, allow_large, is_large, is_zero);
}
// Primitive aligned allocation from the OS.
// This function guarantees the allocated memory is aligned.
static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid) {
static void* mi_os_prim_alloc_aligned(mi_subproc_t* subproc, size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid) {
mi_assert_internal(memid!=NULL);
mi_assert_internal(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0));
mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
@@ -343,8 +361,12 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
if (!(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0))) return NULL;
size = _mi_align_up(size, _mi_os_page_size());
#if MI_SIZE_SIZE >= 8
const bool try_direct_alloc = true;
#else
// try a direct allocation if the alignment is below the default, or less than or equal to 1/4 fraction of the size.
const bool try_direct_alloc = (alignment <= mi_os_mem_config.alloc_granularity || alignment <= size/4);
#endif
bool os_is_large = false;
bool os_is_zero = false;
@@ -352,7 +374,7 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
size_t os_size = size;
void* p = NULL;
if (try_direct_alloc) {
p = mi_os_prim_alloc(size, alignment, commit, allow_large, &os_is_large, &os_is_zero);
p = mi_os_prim_alloc(subproc, size, alignment, commit, allow_large, &os_is_large, &os_is_zero);
}
// aligned already?
@@ -366,33 +388,34 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
_mi_warning_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit);
}
#endif
if (p != NULL) { mi_os_prim_free(p, size, (commit ? size : 0), NULL); }
if (p != NULL) { mi_os_prim_free(subproc, p, size, (commit ? size : 0), true /* adjust so we "forget" the previous reservation */); }
if (size >= (SIZE_MAX - alignment)) return NULL; // overflow
const size_t over_size = size + alignment;
if (!mi_os_mem_config.has_partial_free) { // win32 virtualAlloc cannot free parts of an allocated block
// over-allocate uncommitted (virtual) memory
p = mi_os_prim_alloc(over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, &os_is_large, &os_is_zero);
p = mi_os_prim_alloc(subproc, over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, &os_is_large, &os_is_zero);
if (p == NULL) return NULL;
// set p to the aligned part in the full region
// note: Windows VirtualFree needs the actual base pointer
// this is handled though by having the `base` field in the memid
os_base = p; // remember the base
os_size = over_size;
os_size = over_size; // todo: use size instead as now we over-decrement commit stats on free?
p = _mi_align_up_ptr(p, alignment);
// explicitly commit only the aligned part
if (commit) {
if (!_mi_os_commit(p, size, NULL)) {
mi_os_prim_free(os_base, over_size, 0, NULL);
if (!_mi_os_commit(subproc, p, size, NULL)) {
mi_os_prim_free(subproc, os_base, over_size, 0, true);
return NULL;
}
}
}
else { // mmap can free inside an allocation
// overallocate...
p = mi_os_prim_alloc(over_size, 1, commit, false, &os_is_large, &os_is_zero);
// note: we keep `allow_large` but this only works if we can partially free any large OS pages later on. This seems to be the case though.
p = mi_os_prim_alloc(subproc, over_size, 1, commit, allow_large /* or false? */, &os_is_large, &os_is_zero);
if (p == NULL) return NULL;
// and selectively unmap parts around the over-allocated area.
@@ -401,8 +424,8 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
const size_t mid_size = _mi_align_up(size, _mi_os_page_size());
const size_t post_size = over_size - pre_size - mid_size;
mi_assert_internal(pre_size < over_size&& post_size < over_size&& mid_size >= size);
if (pre_size > 0) { mi_os_prim_free(p, pre_size, (commit ? pre_size : 0), NULL); }
if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0), NULL); }
if (pre_size > 0) { mi_os_prim_free(subproc, p, pre_size, (commit ? pre_size : 0), true /* adjust */); }
if (post_size > 0) { mi_os_prim_free(subproc, (uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0), true /* adjust */); }
// we can return the aligned pointer on `mmap` systems
p = aligned_p;
os_base = aligned_p; // since we freed the pre part, `*base == p`.
@@ -421,13 +444,13 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
OS API: alloc and alloc_aligned
----------------------------------------------------------- */
void* _mi_os_alloc(size_t size, mi_memid_t* memid) {
void* _mi_os_alloc(mi_subproc_t* subproc, size_t size, mi_memid_t* memid) {
*memid = _mi_memid_none();
if (size == 0) return NULL;
size = _mi_os_good_alloc_size(size);
bool os_is_large = false;
bool os_is_zero = false;
void* p = mi_os_prim_alloc(size, 0, true, false, &os_is_large, &os_is_zero);
void* p = mi_os_prim_alloc(subproc, size, 0, true, false, &os_is_large, &os_is_zero);
if (p == NULL) return NULL;
*memid = _mi_memid_create_os(p, size, true, os_is_zero, os_is_large);
@@ -436,7 +459,7 @@ void* _mi_os_alloc(size_t size, mi_memid_t* memid) {
return p;
}
void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid)
void* _mi_os_alloc_aligned(mi_subproc_t* subproc, size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid)
{
MI_UNUSED(&_mi_os_get_aligned_hint); // suppress unused warnings
*memid = _mi_memid_none();
@@ -444,7 +467,7 @@ void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allo
size = _mi_os_good_alloc_size(size);
alignment = _mi_align_up(alignment, _mi_os_page_size());
void* p = mi_os_prim_alloc_aligned(size, alignment, commit, allow_large, memid );
void* p = mi_os_prim_alloc_aligned(subproc, size, alignment, commit, allow_large, memid );
if (p == NULL) return NULL;
mi_assert_internal(memid->mem.os.size >= size);
@@ -454,13 +477,13 @@ void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allo
}
mi_decl_nodiscard static void* mi_os_ensure_zero(void* p, size_t size, mi_memid_t* memid) {
mi_decl_nodiscard static void* mi_os_ensure_zero(mi_subproc_t* subproc, void* p, size_t size, mi_memid_t* memid) {
if (p==NULL || size==0) return p;
// ensure committed
if (!memid->initially_committed) {
bool is_zero = false;
if (!_mi_os_commit(p, size, &is_zero)) {
_mi_os_free(p, size, *memid);
if (!_mi_os_commit(subproc, p, size, &is_zero)) {
_mi_os_free(subproc, p, size, *memid);
return NULL;
}
memid->initially_committed = true;
@@ -472,9 +495,9 @@ mi_decl_nodiscard static void* mi_os_ensure_zero(void* p, size_t size, mi_memid_
return p;
}
void* _mi_os_zalloc(size_t size, mi_memid_t* memid) {
void* p = _mi_os_alloc(size,memid);
return mi_os_ensure_zero(p, size, memid);
void* _mi_os_zalloc(mi_subproc_t* subproc, size_t size, mi_memid_t* memid) {
void* p = _mi_os_alloc(subproc, size,memid);
return mi_os_ensure_zero(subproc, p, size, memid);
}
/* -----------------------------------------------------------
@@ -485,28 +508,29 @@ void* _mi_os_zalloc(size_t size, mi_memid_t* memid) {
to use the actual start of the memory region.
----------------------------------------------------------- */
void* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid) {
void* _mi_os_alloc_aligned_at_offset(mi_subproc_t* subproc, size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid) {
mi_assert(offset <= size);
mi_assert((alignment % _mi_os_page_size()) == 0);
*memid = _mi_memid_none();
if (offset > size) return NULL;
if (offset == 0) {
// regular aligned allocation
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid);
return _mi_os_alloc_aligned(subproc, size, alignment, commit, allow_large, memid);
}
else {
// overallocate to align at an offset
const size_t extra = _mi_align_up(offset, alignment) - offset;
if (size >= SIZE_MAX - extra) return NULL; // too large
const size_t oversize = size + extra;
void* const start = _mi_os_alloc_aligned(oversize, alignment, commit, allow_large, memid);
void* const start = _mi_os_alloc_aligned(subproc, oversize, alignment, commit, allow_large, memid);
if (start == NULL) return NULL;
void* const p = (uint8_t*)start + extra;
mi_assert(_mi_is_aligned((uint8_t*)p + offset, alignment));
// decommit the overallocation at the start
// note: this double counts the decommit when freeing `memid`. Should we keep commit size in the memid as well?
if (commit && extra >= _mi_os_page_size()) {
_mi_os_decommit(start, extra);
_mi_os_decommit(subproc, start, extra);
}
return p;
}
@@ -540,9 +564,10 @@ static void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t*
return mi_os_page_align_areax(true, addr, size, newsize);
}
bool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size) {
bool _mi_os_commit_ex(mi_subproc_t* subproc, void* addr, size_t size, bool* is_zero, size_t stat_already_committed) {
mi_assert_internal(size >= stat_already_committed);
if (is_zero != NULL) { *is_zero = false; }
mi_os_stat_counter_increase(commit_calls, 1);
mi_subproc_stat_counter_increase(subproc, commit_calls, 1);
// page align range
size_t csize;
@@ -565,15 +590,15 @@ bool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size)
if (os_is_zero) { mi_track_mem_defined(start,csize); }
else { mi_track_mem_undefined(start,csize); }
#endif
mi_os_stat_increase(committed, stat_size); // use size for precise commit vs. decommit
mi_subproc_stat_increase(subproc, committed, size - stat_already_committed); // for precise commit vs. decommit
return true;
}
bool _mi_os_commit(void* addr, size_t size, bool* is_zero) {
return _mi_os_commit_ex(addr, size, is_zero, size);
bool _mi_os_commit(mi_subproc_t* subproc, void* addr, size_t size, bool* is_zero) {
return _mi_os_commit_ex(subproc, addr, size, is_zero, 0);
}
static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, size_t stat_size) {
static bool mi_os_decommit_ex(mi_subproc_t* subproc, void* addr, size_t size, bool* needs_recommit, size_t stat_size) {
mi_assert_internal(needs_recommit!=NULL);
// page align
@@ -588,15 +613,15 @@ static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, siz
_mi_warning_message("cannot decommit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize);
}
else if (*needs_recommit) {
mi_os_stat_decrease(committed, stat_size);
mi_subproc_stat_decrease(subproc, committed, stat_size);
}
mi_assert_internal(err == 0);
return (err == 0);
}
bool _mi_os_decommit(void* addr, size_t size) {
bool _mi_os_decommit(mi_subproc_t* subproc, void* addr, size_t size) {
bool needs_recommit;
return mi_os_decommit_ex(addr, size, &needs_recommit, size);
return mi_os_decommit_ex(subproc, addr, size, &needs_recommit, size);
}
@@ -604,13 +629,13 @@ bool _mi_os_decommit(void* addr, size_t size) {
// but may be used later again. This will release physical memory
// pages and reduce swapping while keeping the memory committed.
// We page align to a conservative area inside the range to reset.
bool _mi_os_reset(void* addr, size_t size) {
bool _mi_os_reset(mi_subproc_t* subproc, void* addr, size_t size) {
// page align conservatively within the range
size_t csize;
void* start = mi_os_page_align_area_conservative(addr, size, &csize);
if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr)
mi_os_stat_counter_increase(reset, csize);
mi_os_stat_counter_increase(reset_calls, 1);
mi_subproc_stat_counter_increase(subproc, reset, csize);
mi_subproc_stat_counter_increase(subproc, reset_calls, 1);
#if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN
memset(start, 0, csize); // pretend it is eagerly reset
@@ -624,7 +649,8 @@ bool _mi_os_reset(void* addr, size_t size) {
}
void _mi_os_reuse( void* addr, size_t size ) {
void _mi_os_reuse( mi_subproc_t* subproc, void* addr, size_t size ) {
MI_UNUSED(subproc);
// page align conservatively within the range
size_t csize = 0;
void* const start = mi_os_page_align_area_conservative(addr, size, &csize);
@@ -637,11 +663,11 @@ void _mi_os_reuse( void* addr, size_t size ) {
// either resets or decommits memory, returns true if the memory needs
// to be recommitted if it is to be re-used later on.
bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size, mi_commit_fun_t* commit_fun, void* commit_fun_arg)
bool _mi_os_purge_ex(mi_subproc_t* subproc, void* p, size_t size, bool allow_reset, size_t stat_size, mi_commit_fun_t* commit_fun, void* commit_fun_arg)
{
if (mi_option_get(mi_option_purge_delay) < 0) return false; // is purging allowed?
mi_os_stat_counter_increase(purge_calls, 1);
mi_os_stat_counter_increase(purged, size);
mi_subproc_stat_counter_increase(subproc, purge_calls, 1);
mi_subproc_stat_counter_increase(subproc, purged, size);
if (commit_fun != NULL) {
bool decommitted = (*commit_fun)(false, p, size, NULL, commit_fun_arg);
@@ -651,12 +677,12 @@ bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size, m
!_mi_preloading()) // don't decommit during preloading (unsafe)
{
bool needs_recommit = true;
mi_os_decommit_ex(p, size, &needs_recommit, stat_size);
mi_os_decommit_ex(subproc, p, size, &needs_recommit, stat_size);
return needs_recommit;
}
else {
if (allow_reset) { // this can sometimes be not allowed if the range is not fully committed (on Windows, we cannot reset uncommitted memory)
_mi_os_reset(p, size);
_mi_os_reset(subproc, p, size);
}
return false; // needs no recommit
}
@@ -664,8 +690,8 @@ bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size, m
// either resets or decommits memory, returns true if the memory needs
// to be recommitted if it is to be re-used later on.
bool _mi_os_purge(void* p, size_t size) {
return _mi_os_purge_ex(p, size, true, size, NULL, NULL);
bool _mi_os_purge(mi_subproc_t* subproc, void* p, size_t size) {
return _mi_os_purge_ex(subproc, p, size, true, size, NULL, NULL);
}
@@ -704,14 +730,18 @@ and possibly associated with a specific NUMA node. (use `numa_node>=0`)
#define MI_HUGE_OS_PAGE_SIZE (MI_GiB)
#if (MI_INTPTR_SIZE >= 8)
#if (MI_SIZE_SIZE >= 8)
// To ensure proper alignment, use our own area for huge OS pages
static mi_decl_cache_align _Atomic(uintptr_t) mi_huge_start; // = 0
// Claim an aligned address range for huge pages
static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
if (total_size != NULL) *total_size = 0;
const size_t size = pages * MI_HUGE_OS_PAGE_SIZE;
size_t size = 0;
if (mi_mul_overflow(pages,MI_HUGE_OS_PAGE_SIZE,&size)) {
_mi_warning_message("too many huge pages requested: %zu\n", pages);
return NULL;
}
uintptr_t start = 0;
uintptr_t end = 0;
@@ -720,7 +750,7 @@ static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
start = huge_start;
if (start == 0) {
// Initialize the start address after the 32TiB area
start = ((uintptr_t)8 << 40); // 8TiB virtual start address
start = ((uintptr_t)32 << 40); // 32TiB virtual start address (after addresses returned by _mi_os_get_aligned_hint)
#if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode
mi_theap_t* const theap = _mi_theap_default(); // don't use `mi_theap_get_default()` as that can cause allocation recursively (issue #1267)
if (mi_theap_is_initialized(theap)) { // todo: or no hint at all if we lack randomness?
@@ -728,7 +758,7 @@ static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF)); // (randomly 12bits)*1GiB == between 0 to 4TiB
}
else {
_mi_warning_message("failed to randomize the start address of huge pages allocation (%zu bytes at %p)", size, start);
_mi_warning_message("failed to randomize the start address of huge pages allocation (%zu bytes at %p)", size, (void*)start);
}
#endif
}
@@ -747,7 +777,7 @@ static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
#endif
// Allocate MI_ARENA_SLICE_ALIGN aligned huge pages
void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_msecs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid) {
void* _mi_os_alloc_huge_os_pages(mi_subproc_t* subproc, size_t pages, int numa_node, mi_msecs_t max_msecs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid) {
*memid = _mi_memid_none();
if (psize != NULL) *psize = 0;
if (pages_reserved != NULL) *pages_reserved = 0;
@@ -778,21 +808,21 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
// no success, issue a warning and break
if (p != NULL) {
_mi_warning_message("could not allocate contiguous huge OS page %zu at %p\n", page, addr);
mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, NULL);
mi_os_prim_free(subproc, p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, true /* adjust */);
}
break;
}
// success, record it
page++; // increase before timeout check (see issue #711)
mi_os_stat_increase(committed, MI_HUGE_OS_PAGE_SIZE);
mi_os_stat_increase(reserved, MI_HUGE_OS_PAGE_SIZE);
mi_subproc_stat_increase(subproc, committed, MI_HUGE_OS_PAGE_SIZE);
mi_subproc_stat_increase(subproc, reserved, MI_HUGE_OS_PAGE_SIZE);
// check for timeout
if (max_msecs > 0) {
mi_msecs_t elapsed = _mi_clock_end(start_t);
if (page >= 1) {
mi_msecs_t estimate = ((elapsed / (page+1)) * pages);
mi_msecs_t estimate = ((elapsed / (page==0 ? 1 : page)) * pages);
if (estimate > 2*max_msecs) { // seems like we are going to timeout, break
elapsed = max_msecs + 1;
}
@@ -821,11 +851,11 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
// free every huge page in a range individually (as we allocated per page)
// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems.
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_subproc_t* subproc) {
static void mi_os_free_huge_os_pages(mi_subproc_t* subproc, void* p, size_t size) {
if (p==NULL || size==0) return;
uint8_t* base = (uint8_t*)p;
while (size >= MI_HUGE_OS_PAGE_SIZE) {
mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, subproc);
mi_os_prim_free(subproc, base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE, false /* adjust? */);
size -= MI_HUGE_OS_PAGE_SIZE;
base += MI_HUGE_OS_PAGE_SIZE;
}
+228 -158
View File
@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------
Copyright (c) 2023-2025, Microsoft Research, Daan Leijen
Copyright (c) 2023-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -25,8 +25,11 @@ static void mi_page_map_cannot_commit(void) {
// A full 256 TiB address space (48 bit) needs a 4 GiB page map.
// A full 4 GiB address space (32 bit) needs only a 64 KiB page map.
mi_decl_cache_align uint8_t* _mi_page_map = NULL;
static void* mi_page_map_max_address = NULL;
// Use an initial empty page map so `free(NULL)` works even if mimalloc is not yet initialized (issue #1341)
static uint8_t mi_page_map_empty[1] = { 1 }; // _mi_ptr_page(NULL) == NULL
mi_decl_hidden mi_decl_cache_align _Atomic(uint8_t*) _mi_page_map = mi_page_map_empty;
mi_decl_hidden _Atomic(void*) _mi_page_map_max_address = NULL;
static mi_memid_t mi_page_map_memid;
#define MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT MI_ARENA_SLICE_SIZE
@@ -35,7 +38,7 @@ static mi_bitmap_t* mi_page_map_commit; // one bit per committed 64 KiB entries
mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count);
bool _mi_page_map_init(void) {
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_MAX_VABITS);
if (vbits == 0) {
vbits = _mi_os_virtual_address_bits();
#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB
@@ -45,15 +48,22 @@ bool _mi_page_map_init(void) {
if (vbits < MI_ARENA_SLICE_SHIFT) {
vbits = MI_ARENA_SLICE_SHIFT;
}
if (vbits < MI_MIN_VABITS) { // cover at least this much for a faster _mi_checked_ptr
vbits = MI_MIN_VABITS;
}
if (vbits > MI_MAX_VABITS) { // limit page map size even if more virtual addresses are available
vbits = MI_MAX_VABITS;
}
// Allocate the page map and commit bits
mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));
mi_atomic_store_ptr_release(void, &_mi_page_map_max_address, (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits)));
const size_t page_map_size = (MI_ZU(1) << (vbits - MI_ARENA_SLICE_SHIFT));
const bool commit = (page_map_size <= 1*MI_MiB || mi_option_is_enabled(mi_option_pagemap_commit)); // _mi_os_has_overcommit(); // commit on-access on Linux systems?
const size_t commit_bits = _mi_divide_up(page_map_size, MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);
const size_t bitmap_size = (commit ? 0 : mi_bitmap_size(commit_bits, NULL));
const size_t reserve_size = bitmap_size + page_map_size;
uint8_t* const base = (uint8_t*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);
mi_subproc_t* const subproc = _mi_subproc_main();
uint8_t* const base = (uint8_t*)_mi_os_alloc_aligned(subproc, reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);
if (base==NULL) {
_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);
return false;
@@ -64,13 +74,13 @@ bool _mi_page_map_init(void) {
}
if (bitmap_size > 0) {
mi_page_map_commit = (mi_bitmap_t*)base;
if (!_mi_os_commit(mi_page_map_commit, bitmap_size, NULL)) {
if (!_mi_os_commit(subproc, mi_page_map_commit, bitmap_size, NULL)) {
mi_page_map_cannot_commit();
return false;
}
mi_bitmap_init(mi_page_map_commit, commit_bits, true);
}
_mi_page_map = base + bitmap_size;
mi_atomic_store_ptr_release(uint8_t,&_mi_page_map, base + bitmap_size);
// commit the first part so NULL pointers get resolved without an access violation
if (!commit) {
@@ -79,19 +89,18 @@ bool _mi_page_map_init(void) {
return false;
}
}
_mi_page_map[0] = 1; // so _mi_ptr_page(NULL) == NULL
mi_atomic_load_ptr_relaxed(uint8_t, &_mi_page_map)[0] = 1; // so _mi_ptr_page(NULL) == NULL
mi_assert_internal(_mi_ptr_page(NULL)==NULL);
return true;
}
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
mi_assert_internal(subproc != NULL);
mi_assert_internal(_mi_page_map != NULL);
if (_mi_page_map == NULL) return;
_mi_os_free_ex(mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
_mi_page_map = NULL;
void _mi_page_map_unsafe_destroy(void) {
mi_assert_internal(mi_atomic_load_ptr_relaxed(uint8_t, &_mi_page_map) != NULL);
if (mi_atomic_load_ptr_relaxed(uint8_t, &_mi_page_map) == NULL) return;
_mi_os_free_ex(_mi_subproc_main(), mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, true, mi_page_map_memid);
mi_atomic_store_ptr_release(uint8_t, &_mi_page_map, NULL);
mi_page_map_commit = NULL;
mi_page_map_max_address = NULL;
mi_atomic_store_ptr_release(void, &_mi_page_map_max_address, NULL);
mi_page_map_memid = _mi_memid_none();
}
@@ -100,6 +109,7 @@ static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count) {
// is the page map area that contains the page address committed?
// we always set the commit bits so we can track what ranges are in-use.
// we only actually commit if the map wasn't committed fully already.
uint8_t* const page_map = mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map);
if (mi_page_map_commit != NULL) {
const size_t commit_idx = idx / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
const size_t commit_idx_hi = (idx + slice_count - 1) / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
@@ -107,9 +117,9 @@ static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count) {
if (mi_bitmap_is_clear(mi_page_map_commit, i)) {
// this may race, in which case we do multiple commits (which is ok)
bool is_zero;
uint8_t* const start = _mi_page_map + (i * MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);
uint8_t* const start = page_map + (i * MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);
const size_t size = MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
if (!_mi_os_commit(start, size, &is_zero)) {
if (!_mi_os_commit(_mi_subproc_main(), start, size, &is_zero)) {
mi_page_map_cannot_commit();
return false;
}
@@ -119,8 +129,8 @@ static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count) {
}
}
#if MI_DEBUG > 0
_mi_page_map[idx] = 0;
_mi_page_map[idx+slice_count-1] = 0;
page_map[idx] = 0;
page_map[idx+slice_count-1] = 0;
#endif
return true;
}
@@ -137,11 +147,13 @@ static size_t mi_page_map_get_idx(mi_page_t* page, uint8_t** page_start, size_t*
bool _mi_page_map_register(mi_page_t* page) {
mi_assert_internal(page != NULL);
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
if mi_unlikely(_mi_page_map == NULL) {
mi_assert_internal(mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map) != NULL); // should be initialized before multi-thread access!
uint8_t* page_map = mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map);
if mi_unlikely(mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map) == NULL) {
if (!_mi_page_map_init()) return false;
page_map = mi_atomic_load_ptr_acquire(uint8_t,&_mi_page_map);
}
mi_assert(_mi_page_map!=NULL);
mi_assert(page_map!=NULL);
uint8_t* page_start;
size_t slice_count;
const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);
@@ -153,37 +165,42 @@ bool _mi_page_map_register(mi_page_t* page) {
// set the offsets
for (size_t i = 0; i < slice_count; i++) {
mi_assert_internal(i < 128);
_mi_page_map[idx + i] = (uint8_t)(i+1);
page_map[idx + i] = (uint8_t)(i+1);
}
return true;
}
void _mi_page_map_unregister(mi_page_t* page) {
mi_assert_internal(_mi_page_map != NULL);
uint8_t* const page_map = mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map);
mi_assert_internal(page_map != NULL);
if (page_map == NULL) return;
// get index and count
uint8_t* page_start;
size_t slice_count;
const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);
// unset the offsets
_mi_memzero(_mi_page_map + idx, slice_count);
_mi_memzero(page_map + idx, slice_count);
}
void _mi_page_map_unregister_range(void* start, size_t size) {
uint8_t* const page_map = mi_atomic_load_ptr_relaxed(uint8_t,&_mi_page_map);
mi_assert_internal(page_map!=NULL);
if (page_map == NULL) return;
const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);
const uintptr_t index = _mi_page_map_index(start);
// todo: scan the commit bits and clear only those ranges?
if (!mi_page_map_ensure_committed(index, slice_count)) { // we commit the range in total;
return;
}
_mi_memzero(&_mi_page_map[index], slice_count);
_mi_memzero(&page_map[index], slice_count);
}
mi_page_t* _mi_safe_ptr_page(const void* p) {
if mi_unlikely(p >= mi_page_map_max_address) return NULL;
if mi_unlikely(p >= mi_atomic_load_ptr_relaxed(void, &_mi_page_map_max_address)) return NULL;
const uintptr_t idx = _mi_page_map_index(p);
if mi_unlikely(mi_page_map_commit != NULL && !mi_bitmap_is_set(mi_page_map_commit, idx/MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT)) return NULL;
const uintptr_t ofs = _mi_page_map[idx];
const uintptr_t ofs = _mi_page_map_at(idx);
if mi_unlikely(ofs == 0) return NULL;
return (mi_page_t*)((((uintptr_t)p >> MI_ARENA_SLICE_SHIFT) - ofs + 1) << MI_ARENA_SLICE_SHIFT);
}
@@ -196,43 +213,64 @@ mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_att
// A 2-level page map
#define MI_PAGE_MAP_SUB_SIZE (MI_PAGE_MAP_SUB_COUNT * sizeof(mi_page_t*))
#define MI_PAGE_MAP_ENTRIES_PER_CBIT (MI_PAGE_MAP_COUNT < MI_BFIELD_BITS ? 1 : (MI_PAGE_MAP_COUNT / MI_BFIELD_BITS))
mi_decl_cache_align _Atomic(mi_submap_t)* _mi_page_map;
static size_t mi_page_map_count;
static void* mi_page_map_max_address;
static mi_memid_t mi_page_map_memid;
static mi_lock_t mi_page_map_lock;
// Use an initial empty page map so `free(NULL)` works even if mimalloc is not yet initialized (issue #1341)
static mi_page_map_t mi_page_map_empty = {
MI_ATOMIC_VAR_INIT(1),
sizeof(mi_page_map_t),
MI_MEMID_STATIC,
MI_LOCK_INITIALIZER,
{ MI_ATOMIC_VAR_INIT(NULL) }
};
// divide the main map in 64 (`MI_BFIELD_BITS`) parts commit those parts on demand
static _Atomic(mi_bfield_t) mi_page_map_commit;
mi_decl_hidden mi_decl_cache_align _Atomic(mi_page_map_t*) __mi_page_map = MI_ATOMIC_VAR_INIT(&mi_page_map_empty);
mi_decl_nodiscard static inline bool mi_page_map_is_committed(size_t idx, size_t* pbit_idx) {
mi_bfield_t commit = mi_atomic_load_relaxed(&mi_page_map_commit);
const size_t bit_idx = idx/MI_PAGE_MAP_ENTRIES_PER_CBIT;
mi_assert_internal(bit_idx < MI_BFIELD_BITS);
if (pbit_idx != NULL) { *pbit_idx = bit_idx; }
return ((commit & (MI_ZU(1) << bit_idx)) != 0);
static size_t mi_page_map_count_of_size(size_t size) {
return (size < sizeof(mi_page_map_t) ? 0 : 1 + (size - sizeof(mi_page_map_t))/sizeof(mi_submap_t));
}
mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, mi_submap_t* submap) {
mi_assert_internal(submap!=NULL && *submap==NULL);
size_t bit_idx;
if mi_unlikely(!mi_page_map_is_committed(idx, &bit_idx)) {
uint8_t* start = (uint8_t*)&_mi_page_map[bit_idx * MI_PAGE_MAP_ENTRIES_PER_CBIT];
if (!_mi_os_commit(start, MI_PAGE_MAP_ENTRIES_PER_CBIT * sizeof(mi_submap_t), NULL)) {
mi_page_map_cannot_commit();
return false;
}
mi_atomic_or_acq_rel(&mi_page_map_commit, MI_ZU(1) << bit_idx);
// static void* mi_page_map_addr_of_index(size_t idx) {
// return (void*)((uintptr_t)idx * MI_PAGE_MAP_SUB_COUNT * MI_ARENA_SLICE_SIZE);
// }
mi_decl_nodiscard static mi_decl_noinline bool mi_page_map_commit_entries(mi_page_map_t* pmap, size_t required_idx) {
const size_t reserved_count = mi_page_map_count_of_size(pmap->reserved_size);
if mi_unlikely(required_idx >= reserved_count) {
mi_page_map_cannot_commit();
return false;
}
*submap = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // acquire _mi_page_map_at(idx);
size_t commit_size = _mi_align_up( sizeof(mi_page_map_t) + (required_idx * sizeof(mi_submap_t)), MI_ARENA_SLICE_SIZE );
if (pmap->reserved_size < commit_size) { commit_size = pmap->reserved_size; }
const size_t commit_count = mi_page_map_count_of_size(commit_size);
mi_assert_internal(commit_count > required_idx);
mi_assert_internal(commit_count <= reserved_count);
// note: we rely on uncommitted memory to be zero initialized on the first commit (and further concurrent commits leave the memory as is).
bool is_zero;
if mi_unlikely(!_mi_os_commit(_mi_subproc_main(), pmap, commit_size, &is_zero)) {
mi_page_map_cannot_commit();
return false;
}
mi_assert_internal(is_zero || pmap->memid.initially_zero);
mi_atomic_store_release(&pmap->committed_count, commit_count);
// mi_atomic_store_release(&pmap->committed_addr, mi_page_map_addr_of_index(commit_count));
return true;
}
// initialize the page map
bool _mi_page_map_init(void) {
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);
mi_decl_nodiscard static bool mi_page_map_ensure_committed(mi_page_map_t* pmap, size_t idx, mi_submap_t* submap) {
mi_assert_internal(submap!=NULL && *submap==NULL);
if mi_unlikely(idx >= mi_atomic_load_relaxed(&pmap->committed_count)) {
if (idx >= mi_atomic_load_acquire(&pmap->committed_count)) {
if (!mi_page_map_commit_entries(pmap,idx)) return false;
mi_assert_internal(idx < mi_atomic_load_relaxed(&pmap->committed_count));
}
}
*submap = mi_atomic_load_ptr_acquire(mi_page_t*, &pmap->submaps[idx]);
return true;
}
// initialize the page map
static bool mi_page_map_init_once(void) {
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_MAX_VABITS);
if (vbits == 0) {
vbits = _mi_os_virtual_address_bits();
#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB
@@ -242,111 +280,145 @@ bool _mi_page_map_init(void) {
if (vbits < MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT) {
vbits = MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT;
}
if (vbits < MI_MIN_VABITS) { // cover at least this much for a faster _mi_checked_ptr
vbits = MI_MIN_VABITS;
}
if (vbits > MI_MAX_VABITS) { // limit page map size even if more virtual addresses are available
vbits = MI_MAX_VABITS;
}
// Allocate the page map and commit bits
mi_assert(MI_MAX_VABITS >= vbits);
mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));
mi_page_map_count = (MI_ZU(1) << (vbits - MI_PAGE_MAP_SUB_SHIFT - MI_ARENA_SLICE_SHIFT));
mi_assert(mi_page_map_count <= MI_PAGE_MAP_COUNT);
const size_t os_page_size = _mi_os_page_size();
const size_t page_map_size = _mi_align_up( mi_page_map_count * sizeof(mi_page_t**), os_page_size);
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
const size_t reserve_size = page_map_size + submap_size;
#if MI_SECURE
const bool commit = true; // the whole page map is valid and we can reliably check any pointer
#else
const bool commit = page_map_size <= 64*MI_KiB ||
mi_assert(MI_MIN_VABITS <= vbits);
const size_t reserve_count = (MI_ZU(1) << (vbits - MI_PAGE_MAP_SUB_SHIFT - MI_ARENA_SLICE_SHIFT));
const size_t os_page_size = _mi_os_page_size();
const size_t reserve_size = _mi_align_up( sizeof(mi_page_map_t) + ((reserve_count - 1) * sizeof(mi_submap_t)), os_page_size);
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
const size_t extra_reserve_size = reserve_size + submap_size;
const bool commit = (vbits == MI_MIN_VABITS) || (reserve_size <= 64*MI_KiB) || // 42 virtual address bits
mi_option_is_enabled(mi_option_pagemap_commit) || _mi_os_has_overcommit();
#endif
_mi_page_map = (_Atomic(mi_page_t**)*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);
if (_mi_page_map==NULL) {
_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);
mi_subproc_t* const subproc = _mi_subproc_main();
mi_memid_t memid;
mi_page_map_t* const pmap = (mi_page_map_t*)_mi_os_alloc_aligned(subproc, extra_reserve_size, 1, commit, true /* allow large */, &memid);
if mi_unlikely(pmap==NULL) {
_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", extra_reserve_size / MI_KiB);
return false;
}
if (mi_page_map_memid.initially_committed && !mi_page_map_memid.initially_zero) {
_mi_warning_message("internal: the page map was committed but not zero initialized!\n");
_mi_memzero_aligned(_mi_page_map, page_map_size);
// commit
size_t commit_count;
if (memid.initially_committed) {
if (!memid.initially_zero) {
_mi_warning_message("internal: the page map was committed but not zero initialized!\n");
_mi_memzero_aligned(pmap, extra_reserve_size);
memid.initially_zero = true;
}
commit_count = mi_page_map_count_of_size(reserve_size);
}
mi_atomic_store_release(&mi_page_map_commit, (mi_page_map_memid.initially_committed ? ~MI_ZU(0) : MI_ZU(0)));
// ensure there is a submap for the NULL address
mi_page_t** const sub0 = (mi_page_t**)((uint8_t*)_mi_page_map + page_map_size); // we reserved a submap part at the end already
if (!mi_page_map_memid.initially_committed) {
if (!_mi_os_commit(sub0, submap_size, NULL)) { // commit full submap (issue #1087)
else {
// commit first entries up to MI_MIN_VABITS entries
const size_t min_commit_count = (MI_ZU(1) << (MI_MIN_VABITS - MI_PAGE_MAP_SUB_SHIFT - MI_ARENA_SLICE_SHIFT));
const size_t min_commit_size = _mi_align_up( sizeof(mi_page_map_t) + ((min_commit_count-1) * sizeof(mi_submap_t)), os_page_size);
mi_assert_internal(min_commit_size <= reserve_size);
bool is_zero;
if (!_mi_os_commit(subproc,pmap,min_commit_size,&is_zero)) {
mi_page_map_cannot_commit();
_mi_os_free(subproc,pmap,extra_reserve_size,memid);
return false;
};
mi_assert_internal(is_zero || memid.initially_zero);
commit_count = mi_page_map_count_of_size(min_commit_size);
mi_assert_internal(commit_count >= min_commit_count);
}
// ensure there is a submap for the NULL address
mi_page_t** const sub0 = (mi_submap_t)((uint8_t*)pmap + reserve_size); // we reserved a submap part at the end already
if (!memid.initially_committed) {
if (!_mi_os_commit(subproc, sub0, submap_size, NULL)) { // commit full submap (issue #1087)
mi_page_map_cannot_commit();
_mi_os_free(subproc,pmap,extra_reserve_size,memid);
return false;
}
}
if (!mi_page_map_memid.initially_zero) { // initialize low addresses with NULL
if (!memid.initially_zero) { // initialize low addresses with NULL
_mi_memzero_aligned(sub0, submap_size);
}
mi_submap_t nullsub = NULL;
if (!mi_page_map_ensure_committed(0,&nullsub)) {
mi_page_map_cannot_commit();
return false;
}
mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[0], sub0);
mi_lock_init(&mi_page_map_lock); // initialize late in case the lock init causes allocation
// initialize the fields
pmap->memid = memid;
pmap->reserved_size = reserve_size;
mi_lock_init(&pmap->lock);
mi_atomic_store_release(&pmap->committed_count, commit_count);
// mi_atomic_store_release(&pmap->committed_addr, mi_page_map_addr_of_index(commit_count));
mi_atomic_store_ptr_release(mi_page_t*, &pmap->submaps[0], sub0);
mi_atomic_store_ptr_release(mi_page_map_t, &__mi_page_map, pmap);
mi_assert_internal(_mi_ptr_page(NULL)==NULL);
return true;
}
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
mi_assert_internal(subproc != NULL);
mi_assert_internal(_mi_page_map != NULL);
if (_mi_page_map == NULL) return;
mi_lock_done(&mi_page_map_lock);
for (size_t idx = 1; idx < mi_page_map_count; idx++) { // skip entry 0 (as we allocate that submap at the end of the page_map)
// free all sub-maps
if (mi_page_map_is_committed(idx, NULL)) {
mi_submap_t sub = _mi_page_map_at(idx);
if (sub != NULL) {
mi_memid_t memid = _mi_memid_create_os(sub, MI_PAGE_MAP_SUB_SIZE, true, false, false);
_mi_os_free_ex(memid.mem.os.base, memid.mem.os.size, true, memid, subproc);
mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[idx], NULL);
}
}
bool _mi_page_map_init(void) {
bool ok = true;
mi_atomic_do_once {
ok = mi_page_map_init_once();
}
_mi_os_free_ex(_mi_page_map, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
_mi_page_map = NULL;
mi_page_map_count = 0;
mi_page_map_memid = _mi_memid_none();
mi_page_map_max_address = NULL;
mi_atomic_store_release(&mi_page_map_commit, (mi_bfield_t)0);
return ok;
}
mi_decl_nodiscard static bool mi_page_map_ensure_submap_at(size_t idx, mi_submap_t* submap) {
mi_assert_internal(submap!=NULL && *submap==NULL);
mi_submap_t sub = NULL;
if (!mi_page_map_ensure_committed(idx, &sub)) {
return false;
void _mi_page_map_unsafe_destroy(void) {
mi_page_map_t* const pmap = _mi_page_map();
mi_assert_internal(pmap != NULL);
if (pmap == NULL || pmap == &mi_page_map_empty) return;
mi_subproc_t* const subproc = _mi_subproc_main();
mi_lock_done(&pmap->lock);
for (size_t idx = 1; idx < pmap->committed_count; idx++) { // skip entry 0 (as we allocate that submap at the end of the page_map)
// free all sub-maps
mi_submap_t sub = _mi_page_map_at(pmap,idx);
if (sub != NULL) {
mi_memid_t memid = _mi_memid_create_os(sub, MI_PAGE_MAP_SUB_SIZE, true, false, false);
_mi_os_free_ex(subproc, memid.mem.os.base, memid.mem.os.size, true, memid);
mi_atomic_store_ptr_release(mi_page_t*, &pmap->submaps[idx], NULL);
}
}
if mi_unlikely(sub == NULL) {
// sub map not yet allocated, alloc now
mi_lock(&mi_page_map_lock)
_mi_os_free_ex(subproc, pmap, pmap->reserved_size, true, pmap->memid);
mi_atomic_store_ptr_release(mi_page_map_t, &__mi_page_map, &mi_page_map_empty);
}
mi_decl_nodiscard static mi_decl_noinline mi_submap_t mi_page_map_alloc_submap_at(mi_page_map_t* pmap, size_t idx) {
// sub map not yet allocated, alloc now
mi_submap_t sub = NULL;
mi_lock(&pmap->lock)
{
sub = mi_atomic_load_ptr_acquire(mi_page_t*, &pmap->submaps[idx]); // reload
if (sub==NULL) // not yet allocated by another thread?
{
sub = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // reload
if (sub==NULL) // not yet allocated by another thread?
{
mi_memid_t memid;
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
sub = (mi_submap_t)_mi_os_zalloc(submap_size, &memid);
if (sub==NULL) {
_mi_warning_message("internal error: unable to extend the page map\n");
}
else {
mi_submap_t expect = NULL;
if (!mi_atomic_cas_ptr_strong_acq_rel(mi_page_t*, &_mi_page_map[idx], &expect, sub)) {
// another thread already allocated it.. free and continue
_mi_os_free(sub, submap_size, memid);
sub = expect;
}
mi_subproc_t* const subproc = _mi_subproc_main();
mi_memid_t memid;
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
sub = (mi_submap_t)_mi_os_zalloc(subproc, submap_size, &memid);
if (sub==NULL) {
_mi_warning_message("internal error: unable to extend the page map\n");
}
else {
mi_submap_t expect = NULL;
if (!mi_atomic_cas_ptr_strong_acq_rel(mi_page_t*, &pmap->submaps[idx], &expect, sub)) {
// another thread already allocated it.. free and continue
_mi_os_free(subproc, sub, submap_size, memid);
sub = expect;
}
}
}
}
return sub;
}
mi_decl_nodiscard static bool mi_page_map_ensure_submap_at(mi_page_map_t* pmap, size_t idx, mi_submap_t* submap) {
mi_assert_internal(submap!=NULL && *submap==NULL);
mi_submap_t sub = NULL;
if (!mi_page_map_ensure_committed(pmap, idx, &sub)) {
return false;
}
if mi_unlikely(sub==NULL) {
sub = mi_page_map_alloc_submap_at(pmap, idx);
if (sub==NULL) return false; // unable to allocate the submap..
}
mi_assert_internal(sub!=NULL);
@@ -354,11 +426,11 @@ mi_decl_nodiscard static bool mi_page_map_ensure_submap_at(size_t idx, mi_submap
return true;
}
static bool mi_page_map_set_range_prim(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
static bool mi_page_map_set_range_prim(mi_page_map_t* pmap, mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
// is the page map area that contains the page address committed?
while (slice_count > 0) {
mi_submap_t sub = NULL;
if (!mi_page_map_ensure_submap_at(idx, &sub)) {
if (!mi_page_map_ensure_submap_at(pmap, idx, &sub)) {
return false;
};
mi_assert_internal(sub!=NULL);
@@ -374,11 +446,11 @@ static bool mi_page_map_set_range_prim(mi_page_t* page, size_t idx, size_t sub_i
return true;
}
static bool mi_page_map_set_range(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
if mi_unlikely(!mi_page_map_set_range_prim(page,idx,sub_idx,slice_count)) {
static bool mi_page_map_set_range(mi_page_map_t* pmap, mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
if mi_unlikely(!mi_page_map_set_range_prim(pmap, page,idx,sub_idx,slice_count)) {
// failed to commit, call again to reset the page pointer if needed
if (page!=NULL) {
mi_page_map_set_range_prim(NULL,idx,sub_idx,slice_count);
mi_page_map_set_range_prim(pmap,NULL,idx,sub_idx,slice_count);
}
return false;
}
@@ -396,48 +468,46 @@ static size_t mi_page_map_get_idx(mi_page_t* page, size_t* sub_idx, size_t* slic
bool _mi_page_map_register(mi_page_t* page) {
mi_assert_internal(page != NULL);
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
if mi_unlikely(_mi_page_map == NULL) {
mi_page_map_t* pmap = _mi_page_map();
mi_assert_internal(pmap != NULL); // should be initialized before multi-thread access!
if mi_unlikely(pmap == NULL) {
if (!_mi_page_map_init()) return false;
pmap = mi_atomic_load_ptr_acquire(mi_page_map_t,&__mi_page_map);
}
mi_assert(_mi_page_map!=NULL);
mi_assert(pmap!=NULL);
size_t slice_count;
size_t sub_idx;
const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);
return mi_page_map_set_range(page, idx, sub_idx, slice_count);
return mi_page_map_set_range(pmap, page, idx, sub_idx, slice_count);
}
void _mi_page_map_unregister(mi_page_t* page) {
mi_assert_internal(_mi_page_map != NULL);
mi_assert_internal(page != NULL);
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
if mi_unlikely(_mi_page_map == NULL) return;
mi_page_map_t* const pmap = _mi_page_map();
// note: should proceed even if the page was not registered yet (for failure paths in page allocation in `arena.c`)
if mi_unlikely(pmap == NULL) return;
// get index and count
size_t slice_count;
size_t sub_idx;
const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);
// unset the offsets
mi_page_map_set_range(NULL, idx, sub_idx, slice_count);
mi_page_map_set_range(pmap, NULL, idx, sub_idx, slice_count);
}
void _mi_page_map_unregister_range(void* start, size_t size) {
if mi_unlikely(_mi_page_map == NULL) return;
mi_page_map_t* const pmap = _mi_page_map();
if mi_unlikely(pmap == NULL) return;
const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);
size_t sub_idx;
const uintptr_t idx = _mi_page_map_index(start, &sub_idx);
mi_page_map_set_range(NULL, idx, sub_idx, slice_count); // todo: avoid committing if not already committed?
mi_page_map_set_range(pmap, NULL, idx, sub_idx, slice_count); // todo: avoid committing if not already committed?
}
// Return NULL for invalid pointers
mi_page_t* _mi_safe_ptr_page(const void* p) {
if (p==NULL) return NULL;
if mi_unlikely(p >= mi_page_map_max_address) return NULL;
size_t sub_idx;
const size_t idx = _mi_page_map_index(p,&sub_idx);
if mi_unlikely(!mi_page_map_is_committed(idx,NULL)) return NULL;
mi_page_t** const sub = _mi_page_map[idx];
if mi_unlikely(sub==NULL) return NULL;
return sub[sub_idx];
return _mi_checked_ptr_page(p);
}
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {
+21 -47
View File
@@ -21,11 +21,11 @@ terms of the MIT license. A copy of the license can be found in the file
Minimal alignment in machine words (i.e. `sizeof(void*)`)
----------------------------------------------------------- */
#if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE)
#if (MI_MAX_ALIGN_SIZE > 4*MI_SIZE_SIZE)
#error "define alignment for more than 4x word size for this platform"
#elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE)
#elif (MI_MAX_ALIGN_SIZE > 2*MI_SIZE_SIZE)
#define MI_ALIGN4W // 4 machine words minimal alignment
#elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE)
#elif (MI_MAX_ALIGN_SIZE > MI_SIZE_SIZE)
#define MI_ALIGN2W // 2 machine words minimal alignment
#else
// ok, default alignment is 1 word
@@ -61,8 +61,8 @@ static inline size_t mi_page_queue_count(const mi_page_queue_t* pq) {
// Returns MI_BIN_HUGE if the size is too large.
// We use `wsize` for the size in "machine word sizes",
// i.e. byte size == `wsize*sizeof(void*)`.
static mi_decl_noinline size_t mi_bin(size_t size) {
size_t wsize = _mi_wsize_from_size(size);
static size_t mi_bin(size_t size) {
size_t wsize = mi_wsize_from_size(size);
#if defined(MI_ALIGN4W)
if mi_likely(wsize <= 4) {
return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes
@@ -112,10 +112,10 @@ size_t _mi_bin_size(size_t bin) {
// Good size for allocation
mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept {
if (size <= MI_LARGE_MAX_OBJ_SIZE) {
if (size <= MI_LARGE_MAX_OBJ_SIZE - MI_PADDING_SIZE) {
return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE));
}
else if (size <= MI_MAX_ALLOC_SIZE) {
else if (size <= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) {
return _mi_align_up(size + MI_PADDING_SIZE,_mi_os_page_size());
}
else {
@@ -152,10 +152,10 @@ bool _mi_page_queue_is_valid(mi_theap_t* theap, const mi_page_queue_t* pq) {
for (mi_page_t* page = pq->first; page != NULL; page = page->next) {
mi_assert_internal(page->prev == prev);
if (mi_page_is_in_full(page)) {
mi_assert_internal(_mi_wsize_from_size(pq->block_size) == MI_LARGE_MAX_OBJ_WSIZE + 2);
mi_assert_internal(mi_wsize_from_size(pq->block_size) == MI_LARGE_MAX_OBJ_WSIZE + 2);
}
else if (mi_page_is_huge(page)) {
mi_assert_internal(_mi_wsize_from_size(pq->block_size) == MI_LARGE_MAX_OBJ_WSIZE + 1);
mi_assert_internal(mi_wsize_from_size(pq->block_size) == MI_LARGE_MAX_OBJ_WSIZE + 1);
}
else {
mi_assert_internal(mi_page_block_size(page) == pq->block_size);
@@ -212,10 +212,10 @@ static inline void mi_theap_queue_first_update(mi_theap_t* theap, const mi_page_
if (size > MI_SMALL_SIZE_MAX) return;
mi_page_t* page = pq->first;
if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty;
if (pq->first == NULL) page = _mi_page_empty_get();
// find index in the right direct page array
const size_t idx = _mi_wsize_from_size(size);
const size_t idx = mi_wsize_from_size(size);
mi_page_t** const pages_free = theap->pages_free_direct;
if (pages_free[idx] == page) return; // already set
@@ -232,7 +232,7 @@ static inline void mi_theap_queue_first_update(mi_theap_t* theap, const mi_page_
while( bin == mi_bin(prev->block_size) && prev > &theap->pages[0]) {
prev--;
}
start = 1 + _mi_wsize_from_size(prev->block_size);
start = 1 + mi_wsize_from_size(prev->block_size);
if (start > idx) start = idx;
}
@@ -341,6 +341,15 @@ static void mi_page_queue_move_to_front(mi_theap_t* theap, mi_page_queue_t* queu
mi_assert_internal(queue->first == page);
}
static void mi_page_queue_move_to_back(mi_theap_t* theap, mi_page_queue_t* queue, mi_page_t* page) {
mi_assert_internal(mi_page_theap(page) == theap);
mi_assert_internal(mi_page_queue_contains(queue, page));
if (queue->last == page) return;
mi_page_queue_remove(queue, page);
mi_page_queue_push_at_end(theap, queue, page);
mi_assert_internal(queue->last == page);
}
static void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) {
mi_assert_internal(page != NULL);
mi_assert_internal(from->count >= 1);
@@ -421,38 +430,3 @@ static void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t
// note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`)
mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end of the `to` queue? */, page);
}
// Only called from `mi_theap_absorb`.
size_t _mi_page_queue_append(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_queue_t* append) {
mi_assert_internal(mi_theap_contains_queue(theap,pq));
mi_assert_internal(pq->block_size == append->block_size);
if (append->first==NULL) return 0;
// set append pages to new theap and count
size_t count = 0;
for (mi_page_t* page = append->first; page != NULL; page = page->next) {
mi_page_set_theap(page, theap);
count++;
}
mi_assert_internal(count == append->count);
if (pq->last==NULL) {
// take over afresh
mi_assert_internal(pq->first==NULL);
pq->first = append->first;
pq->last = append->last;
mi_theap_queue_first_update(theap, pq);
}
else {
// append to end
mi_assert_internal(pq->last!=NULL);
mi_assert_internal(append->first!=NULL);
pq->last->next = append->first;
append->first->prev = pq->last;
pq->last = append->last;
}
pq->count += append->count;
return count;
}
File diff suppressed because it is too large. Load diff
+14 -12
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen, Alon Zakai
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen, Alon Zakai
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -61,7 +61,6 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config) {
extern void emmalloc_free(void*);
int _mi_prim_free(void* addr, size_t size) {
if (size==0) return 0;
emmalloc_free(addr);
return 0;
}
@@ -77,7 +76,7 @@ extern void* emmalloc_memalign(size_t alignment, size_t size);
int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
MI_UNUSED(try_alignment); MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr);
*is_large = false;
// TODO: Track the highest address ever seen; first uses of it are zeroes.
// todo: Track the highest address ever seen; first uses of it are zeroes.
// That assumes no one else uses sbrk but us (they could go up,
// scribble, and then down), but we could assert on that perhaps.
*is_zero = false;
@@ -101,7 +100,7 @@ int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool comm
int _mi_prim_commit(void* addr, size_t size, bool* is_zero) {
MI_UNUSED(addr); MI_UNUSED(size);
// See TODO above.
// See todo above.
*is_zero = false;
return 0;
}
@@ -155,7 +154,8 @@ size_t _mi_prim_numa_node_count(void) {
#include <emscripten/html5.h>
mi_msecs_t _mi_prim_clock_now(void) {
return emscripten_date_now();
// todo: use a monotonic clock instead
return emscripten_date_now();
}
@@ -185,12 +185,12 @@ void _mi_prim_out_stderr( const char* msg) {
// Environment
//----------------------------------------------------------------
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
// For code size reasons, do not support environ customization for now.
MI_UNUSED(name);
MI_UNUSED(result);
MI_UNUSED(result_size);
return false;
return 0; // not found
}
@@ -212,7 +212,7 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
// use pthread local storage keys to detect thread ending
// (and used with MI_TLS_PTHREADS for the default theap)
pthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);
pthread_key_t _mi_heap_default_key = MI_PTHREAD_KEY_INVALID;
static void mi_pthread_done(void* value) {
if (value!=NULL) {
@@ -221,18 +221,20 @@ static void mi_pthread_done(void* value) {
}
void _mi_prim_thread_init_auto_done(void) {
mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));
mi_assert_internal(_mi_heap_default_key == MI_PTHREAD_KEY_INVALID);
pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
}
void _mi_prim_thread_done_auto_done(void) {
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // do not leak the key, see issue #809
pthread_key_delete(_mi_heap_default_key);
pthread_key_t key = _mi_heap_default_key;
if (key != MI_PTHREAD_KEY_INVALID) { // do not leak the key, see issue #809
_mi_heap_default_key = MI_PTHREAD_KEY_INVALID;
pthread_key_delete(key);
}
}
void _mi_prim_thread_associate_default_theap(mi_theap_t* theap) {
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // can happen during recursive invocation on freeBSD
if (_mi_heap_default_key != MI_PTHREAD_KEY_INVALID) { // can happen during recursive invocation on freeBSD
pthread_setspecific(_mi_heap_default_key, theap);
}
}
@@ -7,7 +7,7 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim-tls.h" // _mi_thread_is_initialized
#if defined(MI_MALLOC_OVERRIDE)
#if !defined(__APPLE__)
@@ -71,11 +71,12 @@ static void* zone_valloc(malloc_zone_t* zone, size_t size) {
}
static void zone_free(malloc_zone_t* zone, void* p) {
if (mi_any_heap_contains(p)) {
mi_free(p); // with the page_map and pagemap_commit=1 we can use the regular free
}
else if (!is_mimalloc_zone(zone)) { // can happen due to interpose
zone->free(zone,p);
// during C++ thread shutdown `_pthread_tsd_cleanup` may call `zone_free`
// after mimalloc mi_thread_done, and also on a pointer that was allocated in another subproc.
if mi_unlikely(!mi_cfree(p)) {
if (!is_mimalloc_zone(zone)) { // can happen due to interpose
zone->free(zone,p);
}
}
}
@@ -134,7 +135,6 @@ static boolean_t zone_claimed_address(malloc_zone_t* zone, void* p) {
return mi_is_in_heap_region(p);
}
/* ------------------------------------------------------
Introspection members
------------------------------------------------------ */
@@ -194,6 +194,14 @@ static boolean_t intro_zone_locked(malloc_zone_t* zone) {
return false;
}
// Required whenever the zone advertises version >= 9: macOS calls this from the
// atfork_child handler (_malloc_fork_child) without a NULL check. mimalloc keeps
// no zone-level locks that need reinitializing after fork, so a no-op is safe.
// Leaving it NULL makes the forked child jump to address 0 and crash while forking.
static void intro_reinit_lock(malloc_zone_t* zone) {
MI_UNUSED(zone);
}
/* ------------------------------------------------------
At process start, override the default allocator
@@ -218,6 +226,9 @@ static malloc_introspection_t mi_introspect = {
#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) && !defined(__ppc__)
.statistics = &intro_statistics,
.zone_locked = &intro_zone_locked,
#endif
#if defined(MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12) && !defined(__ppc__)
.reinit_lock = &intro_reinit_lock,
#endif
};
+252
View File
@@ -0,0 +1,252 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim.h"
#include "mimalloc/prim-tls.h"
// --------------------------------------------------------------------------
// Implement fast access to the thread local storage for `_mi_theap_default()`
// and `mi_theap_cached()`. See `include/mimalloc/prim-tls.h` for more info
// on the TLS models.
// --------------------------------------------------------------------------
void _mi_tls_slots_init(void);
void _mi_tls_slots_done(void);
void _mi_theap_default_set(mi_theap_t* theap);
void _mi_theap_cached_set(mi_theap_t* theap);
#if MI_TLS_MODEL_LOCAL
// the thread-local main theap for allocation
mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_default = (mi_theap_t*)&_mi_theap_empty;
// the last used non-main theap
mi_decl_hidden mi_decl_thread mi_theap_t* __mi_theap_cached = (mi_theap_t*)&_mi_theap_empty;
#endif
mi_decl_hidden mi_decl_thread void* __mi_thread_id_helper = NULL;
mi_threadid_t _mi_thread_id(void) mi_attr_noexcept {
const mi_threadid_t tid = _mi_prim_thread_id();
mi_assert_internal( (tid & MI_PAGE_FLAG_MASK) == 0 ); // mimalloc reserves the bottom 2 bits
return tid;
}
// ----------------------------------------------------------------------------
// Setting the default and cached theap
// ----------------------------------------------------------------------------
#if MI_TLS_MODEL_WIN32
// If we can, we use one of the 64 direct TLS slots (but fall back to expansion slots if needed)
// See <https://en.wikipedia.org/wiki/Win32_Thread_Information_Block> for the offsets.
#if MI_SIZE_SIZE==4
#define MI_TLS_DIRECT_FIRST (0x0E10 / MI_SIZE_SIZE)
#else
#define MI_TLS_DIRECT_FIRST (0x1480 / MI_SIZE_SIZE)
#endif
#define MI_TLS_DIRECT_SLOTS (64)
#define MI_TLS_EXPANSION_SLOTS (1024)
#if !MI_WIN_DIRECT_TLS
// We initially use the last of the expansion slots as the default NULL.
// note: this will fail if the program allocates exactly 1024+64 slots with TlsAlloc
// before we are initialized :-( (but this seems quite unlikely).
// (todo: another approach could be to use slot 7 (EnvironmentPointer) as the initial slot as that seems to be always NULL)
#define MI_TLS_INITIAL_SLOT MI_TLS_EXPANSION_SLOT
#define MI_TLS_INITIAL_EXPANSION_SLOT (MI_TLS_EXPANSION_SLOTS-1)
#else
// With direct tls we need an initial NULL slot outside the expansion slots
#define MI_TLS_INITIAL_SLOT (5) // Arbitrary user pointer
#define MI_TLS_INITIAL_EXPANSION_SLOT (MI_TLS_EXPANSION_SLOTS-1) // unused
#endif
// in case of errors assign fixed slots (but since we use EFAULT the program should fail anyways)
#define MI_TLS_ERROR_SLOT (5) // arbitrary user pointer
#define MI_TLS_ERROR_EXPANSION_SLOT (7) // environment pointer (only used for OS/2 emulation)
mi_decl_hidden mi_decl_cache_align _Atomic(size_t) _mi_theap_default_slot = MI_ATOMIC_VAR_INIT(MI_TLS_INITIAL_SLOT);
mi_decl_hidden _Atomic(size_t) _mi_theap_default_expansion_slot = MI_ATOMIC_VAR_INIT(MI_TLS_INITIAL_EXPANSION_SLOT);
mi_decl_hidden _Atomic(size_t) _mi_theap_cached_slot = MI_ATOMIC_VAR_INIT(MI_TLS_INITIAL_SLOT);
mi_decl_hidden _Atomic(size_t) _mi_theap_cached_expansion_slot = MI_ATOMIC_VAR_INIT(MI_TLS_INITIAL_EXPANSION_SLOT);
static DWORD mi_tls_raw_index_default = TLS_OUT_OF_INDEXES;
static DWORD mi_tls_raw_index_cached = TLS_OUT_OF_INDEXES;
static bool mi_win_tls_slot_alloc(_Atomic(size_t)* slot, _Atomic(size_t)* extended, DWORD* raw_index) {
// always write slot before extended due to concurrent readers
const DWORD index = TlsAlloc();
*raw_index = index;
if (index==TLS_OUT_OF_INDEXES) {
mi_atomic_store_release(slot,MI_TLS_ERROR_SLOT);
mi_atomic_store_release(extended,MI_TLS_ERROR_EXPANSION_SLOT);
return false;
}
else if (index<MI_TLS_DIRECT_SLOTS) {
mi_atomic_store_release(slot,index + MI_TLS_DIRECT_FIRST);
mi_atomic_store_release(extended,0);
return true;
}
#if !MI_WIN_DIRECT_TLS
else if (index < MI_TLS_DIRECT_SLOTS + MI_TLS_EXPANSION_SLOTS - 1) { // check maximum number of expansion slots - 1 (as we use the last one as the default)
mi_atomic_store_release(slot, MI_TLS_EXPANSION_SLOT);
mi_atomic_store_release(extended,index - MI_TLS_DIRECT_SLOTS);
return true;
}
#endif
else {
// to high an index for us
_mi_error_message(EFAULT, "returned TLS index was too high (%u)\n", index);
TlsFree(index);
*raw_index = TLS_OUT_OF_INDEXES;
mi_atomic_store_release(slot, MI_TLS_ERROR_SLOT);
mi_atomic_store_release(extended,MI_TLS_ERROR_EXPANSION_SLOT);
return false;
}
}
static void mi_win_tls_slot_free(_Atomic(size_t)*slot, _Atomic(size_t)*extended, DWORD* raw_index) {
if (*raw_index != TLS_OUT_OF_INDEXES) {
mi_atomic_store_release(slot, MI_TLS_ERROR_SLOT);
mi_atomic_store_release(extended, MI_TLS_ERROR_EXPANSION_SLOT);
TlsFree(*raw_index);
*raw_index = TLS_OUT_OF_INDEXES;
}
}
void _mi_tls_slots_init(void) {
mi_atomic_do_once {
bool ok = mi_win_tls_slot_alloc(&_mi_theap_default_slot, &_mi_theap_default_expansion_slot, &mi_tls_raw_index_default);
if (ok) {
ok = mi_win_tls_slot_alloc(&_mi_theap_cached_slot, &_mi_theap_cached_expansion_slot, &mi_tls_raw_index_cached);
}
if (!ok) {
_mi_error_message(EFAULT, "unable to allocate a fast TLS user slot.\n");
}
}
}
void _mi_tls_slots_done(void) {
mi_win_tls_slot_free(&_mi_theap_default_slot, &_mi_theap_default_expansion_slot, &mi_tls_raw_index_default);
mi_win_tls_slot_free(&_mi_theap_cached_slot, &_mi_theap_cached_expansion_slot, &mi_tls_raw_index_cached );
}
static void mi_win_tls_slot_set(size_t slot, size_t extended_slot, void* value) {
mi_assert_internal((slot >= MI_TLS_DIRECT_FIRST && slot < MI_TLS_DIRECT_FIRST + MI_TLS_DIRECT_SLOTS) || slot == MI_TLS_EXPANSION_SLOT);
if (slot < MI_TLS_DIRECT_FIRST + MI_TLS_DIRECT_SLOTS) {
mi_prim_tls_slot_set(slot, value);
}
else {
mi_assert_internal(extended_slot < MI_TLS_EXPANSION_SLOTS);
TlsSetValue((DWORD)(extended_slot + MI_TLS_DIRECT_SLOTS), value); // use TlsSetValue to initialize the TlsExpansion array if needed
}
}
#elif MI_TLS_MODEL_PTHREADS
// only for pthreads for now
mi_decl_hidden _Atomic(pthread_key_t) _mi_theap_default_key = MI_ATOMIC_VAR_INIT(MI_PTHREAD_KEY_INVALID);
mi_decl_hidden _Atomic(pthread_key_t) _mi_theap_cached_key = MI_ATOMIC_VAR_INIT(MI_PTHREAD_KEY_INVALID);
static void mi_theap_cached_key_destroy(void* theapv) {
mi_theap_t* theap = (mi_theap_t*)theapv;
if (theap!=NULL) {
_mi_theap_decref(theap);
}
}
void _mi_tls_slots_init(void) {
mi_atomic_do_once {
pthread_key_t key;
if (_mi_pthread_key_create(&key,NULL,NULL)) { mi_atomic_store_release(&_mi_theap_default_key,key); }
if (_mi_pthread_key_create(&key,&mi_theap_cached_key_destroy,NULL)) { mi_atomic_store_release(&_mi_theap_cached_key,key); }
}
}
void _mi_tls_slots_done(void) {
pthread_key_t key = mi_atomic_exchange_relaxed(&_mi_theap_default_key,MI_PTHREAD_KEY_INVALID);
if (key!=MI_PTHREAD_KEY_INVALID) { pthread_key_delete(key); }
key = mi_atomic_exchange_relaxed(&_mi_theap_cached_key,MI_PTHREAD_KEY_INVALID);
if (key!=MI_PTHREAD_KEY_INVALID) { pthread_key_delete(key); }
}
#elif MI_TLS_MODEL_FIXED
void _mi_tls_slots_init(void) {
mi_atomic_do_once {
mi_theap_t* theap = _mi_theap_default();
if (theap!=NULL) {
_mi_error_message(EINVAL,"fixed TLS slot is already in use (slot %d = %p)", MI_TLS_MODEL_FIXED_DEFAULT, theap);
}
theap = _mi_theap_cached();
if (theap!=NULL) {
_mi_error_message(EINVAL,"fixed TLS slot is already in use (slot %d = %p)", MI_TLS_MODEL_FIXED_CACHED, theap);
}
}
}
void _mi_tls_slots_done(void) {
// nothing
}
#else
void _mi_tls_slots_init(void) {
// nothing
}
void _mi_tls_slots_done(void) {
// nothing
}
#endif
void _mi_theap_cached_set(mi_theap_t* theap) {
mi_theap_t* prev = _mi_theap_cached();
if (prev==theap) return;
// set
_mi_tls_slots_init();
#if MI_TLS_MODEL_LOCAL
__mi_theap_cached = theap;
#elif MI_TLS_MODEL_FIXED
mi_prim_tls_slot_set(MI_TLS_MODEL_FIXED_CACHED, theap);
#elif MI_TLS_MODEL_WIN32
mi_win_tls_slot_set(mi_atomic_load_relaxed(&_mi_theap_cached_slot), mi_atomic_load_relaxed(&_mi_theap_cached_expansion_slot), theap);
#elif MI_TLS_MODEL_PTHREADS
pthread_key_t key = mi_atomic_load_relaxed(&_mi_theap_cached_key);
if (key!=MI_PTHREAD_KEY_INVALID) { pthread_setspecific(key, theap); }
#endif
// update refcounts (so cached theap memory keeps available until no longer cached)
_mi_theap_incref(theap);
_mi_theap_decref(prev);
}
void _mi_theap_default_set(mi_theap_t* theap) {
mi_assert_internal(theap != NULL);
mi_assert_internal(theap->tld != NULL);
mi_assert_internal(mi_theap_matches_thread(theap));
_mi_tls_slots_init();
#if MI_TLS_MODEL_LOCAL
__mi_theap_default = theap;
#elif MI_TLS_MODEL_FIXED
mi_prim_tls_slot_set(MI_TLS_MODEL_FIXED_DEFAULT, theap);
#elif MI_TLS_MODEL_WIN32
mi_win_tls_slot_set(mi_atomic_load_relaxed(&_mi_theap_default_slot), mi_atomic_load_relaxed(&_mi_theap_default_expansion_slot), theap);
#elif MI_TLS_MODEL_PTHREADS
pthread_key_t key = mi_atomic_load_relaxed(&_mi_theap_default_key);
if (key!=MI_PTHREAD_KEY_INVALID) { pthread_setspecific(key, theap); }
#endif
// set theap main if needed
if (mi_theap_is_initialized(theap)) {
// ensure the default theap is passed to `_mi_thread_done` as on some platforms we cannot access TLS at thread termination (as it would allocate again)
_mi_prim_thread_associate_default_theap(theap);
}
}
+4 -5
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -27,11 +27,10 @@ terms of the MIT license. A copy of the license can be found in the file
#endif
// Generic process initialization
#ifndef MI_PRIM_HAS_PROCESS_ATTACH
#if !defined(MI_PRIM_HAS_PROCESS_ATTACH)
#if defined(__GNUC__) || defined(__clang__)
// gcc,clang: use the constructor/destructor attribute
// which for both seem to run before regular constructors/destructors
#if defined(__clang__)
// gcc,clang: use the constructor/destructor attribute with the highest user priority (<=100 is reserved for the compiler itself)
#if defined(__clang__) || (__GNUC__ >= 5 && !defined(__APPLE__)) // gcc has support since 4.2 but not on macOS (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119435)
#define mi_attr_constructor __attribute__((constructor(101)))
#define mi_attr_destructor __attribute__((destructor(101)))
#else
+3
View File
@@ -6,4 +6,7 @@ This is the portability layer where all primitives needed from the OS are define
- `prim.c`: Selects one of `unix/prim.c`, `wasi/prim.c`, or `windows/prim.c` depending on the host platform
(and on macOS, `osx/prim.c` defers to `unix/prim.c`).
- `include/mimalloc/prim-tls.h`: primitive TLS model (thread local, pthreads, etc)
- `prim-tls.c`: TLS model implementation
Note: still work in progress, there may still be places in the sources that still depend on OS ifdef's.
+210 -138
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -41,6 +41,13 @@ terms of the MIT license. A copy of the license can be found in the file
#else
#include <sys/mman.h>
#endif
#if defined(__riscv) || defined(_M_RISCV)
#if defined(MI_HAS_SYS_HWPROBEH)
#include <sys/hwprobe.h>
#elif defined(MI_HAS_ASM_HWPROBEH)
#include <asm/hwprobe.h>
#endif
#endif
#elif defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>
@@ -72,7 +79,7 @@ terms of the MIT license. A copy of the license can be found in the file
#define MADV_FREE POSIX_MADV_FREE
#endif
#define MI_UNIX_LARGE_PAGE_SIZE (2*MI_MiB) // TODO: can we query the OS for this?
#define MI_UNIX_LARGE_PAGE_SIZE (2*MI_MiB) // todo: can we query the OS for this?
//------------------------------------------------------------------------------------
// Use syscalls for some primitives to allow for libraries that override open/read/close etc.
@@ -150,13 +157,14 @@ static bool unix_detect_thp(void) {
#if defined(__linux__)
int fd = mi_prim_open("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY);
if (fd >= 0) {
char buf[32];
char buf[64];
ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));
mi_prim_close(fd);
// <https://www.kernel.org/doc/html/latest/admin-guide/mm/transhuge.html>
// between brackets is the current value, for example: always [madvise] never
if (nread >= 1) {
thp_enabled = (_mi_strnstr(buf,32,"[never]") == NULL);
if (nread > 64) { nread = 64; }
thp_enabled = (_mi_strnstr(buf,nread,"[never]") == NULL);
}
}
#endif
@@ -205,6 +213,40 @@ static void unix_detect_physical_memory( size_t page_size, size_t* physical_memo
#endif
}
// Detect the virtual address bits (currently Linux/RISC-V only)
static size_t unix_detect_virtual_address_bits(void) {
#if defined(__riscv) || defined(_M_RISCV)
#if defined(RISCV_HWPROBE_KEY_HIGHEST_VIRT_ADDRESS)
struct riscv_hwprobe probe = { .key = RISCV_HWPROBE_KEY_HIGHEST_VIRT_ADDRESS, };
// Prefer the GNU libc interface if available, as it can also use the VDSO
#if defined(MI_HAS_SYS_HWPROBEH)
if (__riscv_hwprobe(&probe, 1, 0, NULL, 0) == 0)
#else
if (syscall(__NR_riscv_hwprobe, &probe, 1, 0, NULL, 0) == 0)
#endif
{
if (probe.key != -1) { // If a key is unknown to the kernel, its key field will be cleared to -1.
return (MI_SIZE_BITS - mi_clz((uintptr_t)probe.value));
}
}
#endif
// Fallback to checking /proc/cpuinfo for older kernels
const int fd = mi_prim_open("/proc/cpuinfo", O_RDONLY);
if (fd >= 0) {
char buf[2048];
const ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));
mi_prim_close(fd);
if ((nread >= 1) && (nread <= (ssize_t)sizeof(buf))) {
if (_mi_strnstr(buf, nread, "sv39")) { return 39; }
else if (_mi_strnstr(buf, nread, "sv48")) { return 48; }
else if (_mi_strnstr(buf, nread, "sv57")) { return 57; }
}
}
#endif // riscv
// default
return MI_MAX_VABITS;
}
void _mi_prim_mem_init( mi_os_mem_config_t* config )
{
long psize = sysconf(_SC_PAGESIZE);
@@ -218,6 +260,7 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config )
config->has_partial_free = true; // mmap can free in parts
config->has_virtual_reserve = true; // todo: check if this true for NetBSD? (for anonymous mmap with PROT_NONE)
config->has_transparent_huge_pages = unix_detect_thp();
config->virtual_address_bits = unix_detect_virtual_address_bits();
// disable transparent huge pages for this process?
#if (defined(__linux__) || defined(__ANDROID__)) && defined(PR_GET_THP_DISABLE)
@@ -251,7 +294,7 @@ int _mi_prim_free(void* addr, size_t size ) {
// return errno on failure
static int unix_madvise(void* addr, size_t size, int advice) {
#if defined(__sun)
#if defined(__sun) || defined(_AIX)
const int res = madvise((caddr_t)addr, size, advice); // Solaris needs cast (issue #520)
return (res==0 ? 0 : errno);
#elif defined(__QNX__)
@@ -296,7 +339,7 @@ static void* unix_mmap_prim_aligned(void* addr, size_t size, size_t try_alignmen
// fall back to regular mmap
}
#endif
#if (MI_INTPTR_SIZE >= 8) && !defined(MAP_ALIGNED)
#if (MI_SIZE_SIZE >= 8) && !defined(MAP_ALIGNED)
// on 64-bit systems, use the virtual address area after 2TiB for 4MiB aligned allocations
if (addr == NULL) {
void* hint = _mi_os_get_aligned_hint(try_alignment, size);
@@ -333,6 +376,10 @@ static int unix_mmap_fd(void) {
#endif
}
#if defined(MAP_ALIGNED_SUPER) || defined(MAP_HUGETLB) || defined(MAP_HUGE_1GB) || defined(MAP_HUGE_2MB) || defined(VM_FLAGS_SUPERPAGE_SIZE_2MB)
#define MI_OS_HAS_HUGE_PAGES 1
#endif
static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) {
#if !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
@@ -350,6 +397,7 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD
#endif
// huge page allocation
#if MI_OS_HAS_HUGE_PAGES
if (allow_large && (large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages)))) {
static _Atomic(size_t) large_page_try_ok; // = 0;
size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok);
@@ -370,8 +418,8 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
lflags |= MAP_HUGETLB;
#endif
#ifdef MAP_HUGE_1GB
static bool mi_huge_pages_available = true;
if (large_only && (size % MI_GiB) == 0 && mi_huge_pages_available) {
static _Atomic(size_t) mi_huge_1gib_pages_unavailable;
if (large_only && (size % MI_GiB) == 0 && (mi_atomic_load_relaxed(&mi_huge_1gib_pages_unavailable)==0)) {
lflags |= MAP_HUGE_1GB;
}
else
@@ -390,7 +438,7 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, lflags, lfd);
#ifdef MAP_HUGE_1GB
if (p == NULL && (lflags & MAP_HUGE_1GB) == MAP_HUGE_1GB) {
mi_huge_pages_available = false; // don't try huge 1GiB pages again
mi_atomic_store_relaxed(&mi_huge_1gib_pages_unavailable,1); // don't try huge 1GiB pages again
if (large_only) {
_mi_warning_message("unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (errno: %i)\n", errno);
}
@@ -404,7 +452,10 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
}
}
}
}
} // huge pages
#else
MI_UNUSED(large_only);
#endif
// regular allocation
if (p == NULL) {
*is_large = false;
@@ -412,15 +463,17 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
#if !defined(MI_NO_THP)
if (p != NULL && allow_large && mi_option_is_enabled(mi_option_allow_thp) && _mi_os_canuse_large_page(size, try_alignment)) {
#if defined(MADV_HUGEPAGE)
// Many Linux systems don't allow MAP_HUGETLB but they support instead
// transparent huge pages (THP). Generally, it is not required to call `madvise` with MADV_HUGE
// though since properly aligned allocations will already use large pages if available
// in that case -- in particular for our large regions (in `memory.c`).
// However, some systems only allow THP if called with explicit `madvise`, so
// when large OS pages are enabled for mimalloc, we call `madvise` anyways.
if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {
// *is_large = true; // possibly
};
if (_mi_os_canuse_thp()) {
// Many Linux systems don't allow MAP_HUGETLB but they support instead
// transparent huge pages (THP). Generally, it is not required to call `madvise` with MADV_HUGE
// though since properly aligned allocations will already use large pages if available
// in that case -- in particular for our large regions (in `memory.c`).
// However, some systems only allow THP if called with explicit `madvise`, so
// when large OS pages are enabled for mimalloc, we call `madvise` anyways.
if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {
// *is_large = true; // possibly
};
}
#elif defined(__sun)
struct memcntl_mha cmd = {0};
cmd.mha_pagesize = _mi_os_large_page_size();
@@ -452,7 +505,7 @@ int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool comm
//---------------------------------------------
static void unix_mprotect_hint(int err) {
#if defined(__linux__) && (MI_SECURE>=5) // guard page around every mimalloc page
#if defined(__linux__) && (MI_SECURE>=5 || MI_GUARDED) // guard page around every mimalloc page
if (err == ENOMEM) {
_mi_warning_message("The next warning may be caused by a low memory map limit.\n"
" On Linux this is controlled by the vm.max_map_count -- maybe increase it?\n"
@@ -492,38 +545,39 @@ int _mi_prim_reuse(void* start, size_t size) {
int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
int err = 0;
#if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
// decommit on macOS: use MADV_FREE_REUSABLE as it does immediate rss accounting (issue #1097)
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
if (err) { err = unix_madvise(start, size, MADV_DONTNEED); }
#else
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
err = unix_madvise(start, size, MADV_DONTNEED);
#endif
// codebase-memory-mcp local change (2026-10-10): macOS always takes the
// recommit path that upstream reserves for MI_DEBUG / MI_SECURE>2 builds.
// In the release path a purged range stays "committed" and is re-accounted as
// a whole with MADV_FREE_REUSE when the arena hands it out again
// (arena.c, _mi_os_reuse), so macOS charges its pages to phys_footprint
// whether or not they are touched; recommitting instead charges only the
// pages that are written. Measured on the indexer (Apple arm64, macOS 26.6),
// peak phys_footprint release -> this change, wall time unchanged and the
// graph byte-identical: TypeScript 1.98 -> 1.09 GB, kubernetes 2.92 -> 1.88 GB,
// dotnet/runtime 12.7 -> 10.6 GB, linux kernel 16.0 -> 13.5 GB. Other
// platforms keep upstream's behaviour (MADV_DONTNEED drops RSS at once).
#if !MI_DEBUG && MI_SECURE<=2 && !defined(__APPLE__)
*needs_recommit = false;
#if 1
#if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
// decommit on macOS: use MADV_FREE_REUSABLE as it does immediate rss accounting (issue #1097)
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
if (err) { err = unix_madvise(start, size, MADV_DONTNEED); }
#else
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
err = unix_madvise(start, size, MADV_DONTNEED);
#endif
// codebase-memory-mcp local change (2026-10-10): macOS always takes the
// recommit path that upstream reserves for MI_DEBUG / MI_SECURE>2 builds.
// In the release path a purged range stays "committed" and is re-accounted as
// a whole with MADV_FREE_REUSE when the arena hands it out again
// (arena.c, _mi_os_reuse), so macOS charges its pages to phys_footprint
// whether or not they are touched; recommitting instead charges only the
// pages that are written. Measured on the indexer (Apple arm64, macOS 26.6),
// peak phys_footprint release -> this change, wall time unchanged and the
// graph byte-identical: TypeScript 1.98 -> 1.09 GB, kubernetes 2.92 -> 1.88 GB,
// dotnet/runtime 12.7 -> 10.6 GB, linux kernel 16.0 -> 13.5 GB. Other
// platforms keep upstream's behaviour (MADV_DONTNEED drops RSS at once).
#if !MI_DEBUG && MI_SECURE<=2 && !defined(__APPLE__)
*needs_recommit = false;
#else
*needs_recommit = true;
mprotect(start, size, PROT_NONE);
#endif
#else
// decommit: use mmap with MAP_FIXED and PROT_NONE to discard the existing memory (and reduce rss)
*needs_recommit = true;
mprotect(start, size, PROT_NONE);
const int fd = unix_mmap_fd();
void* p = mmap(start, size, PROT_NONE, (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE), fd, 0);
if (p != start) { err = errno; }
#endif
/*
// decommit: use mmap with MAP_FIXED and PROT_NONE to discard the existing memory (and reduce rss)
*needs_recommit = true;
const int fd = unix_mmap_fd();
void* p = mmap(start, size, PROT_NONE, (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE), fd, 0);
if (p != start) { err = errno; }
*/
return err;
}
@@ -569,7 +623,7 @@ int _mi_prim_protect(void* start, size_t size, bool protect) {
// Huge page allocation
//---------------------------------------------
#if (MI_INTPTR_SIZE >= 8) && !defined(__HAIKU__) && !defined(__CYGWIN__)
#if (MI_SIZE_SIZE >= 8) && !defined(__HAIKU__) && !defined(__CYGWIN__)
#ifndef MPOL_PREFERRED
#define MPOL_PREFERRED 1
@@ -590,15 +644,15 @@ int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bo
bool is_large = true;
*is_zero = true;
*addr = unix_mmap(hint_addr, size, MI_ARENA_SLICE_ALIGN, PROT_READ | PROT_WRITE, true, true, &is_large);
if (*addr != NULL && numa_node >= 0 && numa_node < 8*MI_INTPTR_SIZE) { // at most 64 nodes
if (*addr != NULL && numa_node >= 0 && numa_node < (8*MI_SIZE_SIZE - 1)) { // at most 63 nodes
unsigned long numa_mask = (1UL << numa_node);
// TODO: does `mbind` work correctly for huge OS pages? should we
// todo: does `mbind` work correctly for huge OS pages? should we
// use `set_mempolicy` before calling mmap instead?
// see: <https://lkml.org/lkml/2017/2/9/875>
long err = mi_prim_mbind(*addr, size, MPOL_PREFERRED, &numa_mask, 8*MI_INTPTR_SIZE, 0);
long err = mi_prim_mbind(*addr, size, MPOL_PREFERRED, &numa_mask, 8*MI_SIZE_SIZE, 0);
if (err != 0) {
err = errno;
_mi_warning_message("failed to bind huge (1GiB) pages to numa node %d (error: %d (0x%x))\n", numa_node, err, err);
_mi_warning_message("failed to bind huge (1GiB) pages to numa node %d (error: %ld (0x%lx))\n", numa_node, err, err);
}
}
return (*addr != NULL ? 0 : errno);
@@ -623,9 +677,9 @@ int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bo
size_t _mi_prim_numa_node(void) {
#if defined(MI_HAS_SYSCALL_H) && defined(SYS_getcpu)
unsigned long node = 0;
unsigned long ncpu = 0;
long err = syscall(SYS_getcpu, &ncpu, &node, NULL);
unsigned int node = 0;
unsigned int ncpu = 0;
int err = syscall(SYS_getcpu, &ncpu, &node, NULL);
if (err != 0) return 0;
return node;
#else
@@ -635,13 +689,16 @@ size_t _mi_prim_numa_node(void) {
size_t _mi_prim_numa_node_count(void) {
char buf[128];
unsigned node = 0;
for(node = 0; node < 256; node++) {
unsigned last_found = 0;
for(unsigned node = 1; node < 256; node++) {
// enumerate node entries -- todo: it there a more efficient way to do this? (but ensure there is no allocation)
_mi_snprintf(buf, 127, "/sys/devices/system/node/node%u", node + 1);
if (mi_prim_access(buf,R_OK) != 0) break;
_mi_snprintf(buf, 127, "/sys/devices/system/node/node%u", node);
if (mi_prim_access(buf,R_OK) != 0) {
if (node - last_found > 4) break; // allow some sparseness of nodes but not more than 4
}
else { last_found = node; } // highest found node
}
return (node+1);
return last_found + 1;
}
#elif defined(__FreeBSD__) && __FreeBSD_version >= 1200000
@@ -667,7 +724,7 @@ size_t _mi_prim_numa_node_count(void) {
#elif defined(__DragonFly__)
size_t _mi_prim_numa_node(void) {
// TODO: DragonFly does not seem to provide any userland means to get this information.
// todo: DragonFly does not seem to provide any userland means to get this information.
return 0ul;
}
@@ -697,34 +754,38 @@ size_t _mi_prim_numa_node_count(void) {
#include <time.h>
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
mi_msecs_t _mi_prim_clock_now(void) {
struct timespec t;
#ifdef CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &t);
#else
clock_gettime(CLOCK_REALTIME, &t);
#endif
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);
}
#else
// low resolution timer
mi_msecs_t _mi_prim_clock_now(void) {
#if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)
return (mi_msecs_t)clock();
#elif (CLOCKS_PER_SEC < 1000)
return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
static mi_msecs_t mi_prim_clock_now_lowres(void) {
const int64_t ticks = (int64_t)clock();
#if !defined(CLOCKS_PER_SEC)
return ticks;
#else
return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
if (CLOCKS_PER_SEC <= 0 || CLOCKS_PER_SEC == 1000) {
return ticks;
}
else if (CLOCKS_PER_SEC > 0 && CLOCKS_PER_SEC < 1000) {
return ticks * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
}
else {
return ticks / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
}
#endif
}
#endif
mi_msecs_t _mi_prim_clock_now(void) {
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
#ifdef CLOCK_MONOTONIC
const clockid_t clockid = CLOCK_MONOTONIC;
#else
const clockid_t clockid = CLOCK_REALTIME;
#endif
struct timespec t;
if (clock_gettime(clockid,&t) == 0) {
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000L);
}
#endif
return mi_prim_clock_now_lowres();
}
//----------------------------------------------------------------
@@ -751,41 +812,46 @@ static mi_msecs_t timeval_secs(const struct timeval* tv) {
void _mi_prim_process_info(mi_process_info_t* pinfo)
{
struct rusage rusage;
getrusage(RUSAGE_SELF, &rusage);
pinfo->utime = timeval_secs(&rusage.ru_utime);
pinfo->stime = timeval_secs(&rusage.ru_stime);
#if !defined(__HAIKU__)
pinfo->page_faults = rusage.ru_majflt;
#endif
#if defined(__HAIKU__)
// Haiku does not have (yet?) a way to
// get these stats per process
thread_info tid;
area_info mem;
ssize_t c;
get_thread_info(find_thread(0), &tid);
while (get_next_area_info(tid.team, &c, &mem) == B_OK) {
pinfo->peak_rss += mem.ram_size;
}
pinfo->page_faults = 0;
#elif defined(__APPLE__)
pinfo->peak_rss = rusage.ru_maxrss; // macos reports in bytes
#ifdef MACH_TASK_BASIC_INFO
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
pinfo->current_rss = (size_t)info.resident_size;
}
#else
struct task_basic_info info;
mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
pinfo->current_rss = (size_t)info.resident_size;
if (getrusage(RUSAGE_SELF, &rusage) == 0) {
pinfo->utime = timeval_secs(&rusage.ru_utime);
pinfo->stime = timeval_secs(&rusage.ru_stime);
#if !defined(__HAIKU__)
pinfo->page_faults = rusage.ru_majflt;
#endif
#if defined(__APPLE__)
pinfo->peak_rss = rusage.ru_maxrss; // macos reports in bytes
#else
pinfo->peak_rss = rusage.ru_maxrss * 1024; // Linux/BSD report in KiB
#endif
}
#if defined(__HAIKU__)
// Haiku does not have (yet?) a way to
// get these stats per process
thread_info tid;
if (get_thread_info(find_thread(0), &tid) == B_OK) {
area_info mem;
ssize_t c;
while (get_next_area_info(tid.team, &c, &mem) == B_OK) {
pinfo->peak_rss += mem.ram_size;
}
}
pinfo->page_faults = 0;
#elif defined(__APPLE__)
#ifdef MACH_TASK_BASIC_INFO
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
pinfo->current_rss = (size_t)info.resident_size;
}
#else
struct task_basic_info info;
mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
pinfo->current_rss = (size_t)info.resident_size;
}
#endif
#endif
#else
pinfo->peak_rss = rusage.ru_maxrss * 1024; // Linux/BSD report in KiB
#endif
// use defaults for commit
}
@@ -832,28 +898,28 @@ static char** mi_get_environ(void) {
return environ;
}
#endif
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
if (name==NULL) return false;
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
if (name==NULL) return -1;
const size_t len = _mi_strlen(name);
if (len == 0) return false;
if (len == 0) return -1;
char** env = mi_get_environ();
if (env == NULL) return false;
if (env == NULL) return -1;
// compare up to 10000 entries
for (int i = 0; i < 10000 && env[i] != NULL; i++) {
const char* s = env[i];
if (_mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive
// found it
_mi_strlcpy(result, s + len + 1, result_size);
return true;
if (!_mi_strlcpy(result, s + len + 1, result_size)) return -1;
return 1; // success
}
}
return false;
return 0; // not found
}
#else
// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
// cannot call getenv() when still initializing the C runtime.
if (_mi_preloading()) return false;
if (_mi_preloading()) return -1; // error, try again later
const char* s = getenv(name);
if (s == NULL) {
// we check the upper case name too.
@@ -865,9 +931,9 @@ bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
buf[len] = 0;
s = getenv(buf);
}
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return false;
_mi_strlcpy(result, s, result_size);
return true;
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return 0; // not found
if (!_mi_strlcpy(result, s, result_size)) return -1;
return 1; // success
}
#endif // !MI_USE_ENVIRON
@@ -896,7 +962,7 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
return true;
}
#elif defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__) // also for old apple versions < 10.7 (issue #829)
#elif defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__) || defined(__CYGWIN__) // also for old apple versions < 10.7 (issue #829)
#include <sys/types.h>
#include <sys/stat.h>
@@ -928,7 +994,10 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
size_t count = 0;
while(count < buf_len) {
ssize_t ret = mi_prim_read(fd, (char*)buf + count, buf_len - count);
if (ret<=0) {
if (ret==0) {
break;
}
else if (ret<0) {
if (errno!=EAGAIN && errno!=EINTR) break;
}
else {
@@ -956,7 +1025,7 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
// use pthread local storage keys to detect thread ending
// (and used with MI_TLS_PTHREADS for the default theap)
pthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);
pthread_key_t _mi_heap_default_key = MI_PTHREAD_KEY_INVALID;
static void mi_pthread_done(void* value) {
if (value!=NULL) {
@@ -965,22 +1034,25 @@ static void mi_pthread_done(void* value) {
}
void _mi_prim_thread_init_auto_done(void) {
mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));
mi_assert_internal(_mi_heap_default_key == MI_PTHREAD_KEY_INVALID);
pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
}
void _mi_prim_thread_done_auto_done(void) {
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // do not leak the key, see issue #809
pthread_key_delete(_mi_heap_default_key);
pthread_key_t key = _mi_heap_default_key;
if (key != MI_PTHREAD_KEY_INVALID) { // do not leak the key, see issue #809
_mi_heap_default_key = MI_PTHREAD_KEY_INVALID;
pthread_key_delete(key);
}
}
void _mi_prim_thread_associate_default_theap(mi_theap_t* theap) {
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // can happen during recursive invocation on freeBSD
if (_mi_heap_default_key != MI_PTHREAD_KEY_INVALID) { // can happen during recursive invocation on freeBSD
pthread_setspecific(_mi_heap_default_key, theap);
}
}
#else
void _mi_prim_thread_init_auto_done(void) {
+31 -25
View File
@@ -185,32 +185,38 @@ size_t _mi_prim_numa_node_count(void) {
#include <time.h>
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
mi_msecs_t _mi_prim_clock_now(void) {
struct timespec t;
#ifdef CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &t);
#else
clock_gettime(CLOCK_REALTIME, &t);
#endif
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);
}
#else
// low resolution timer
mi_msecs_t _mi_prim_clock_now(void) {
#if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)
return (mi_msecs_t)clock();
#elif (CLOCKS_PER_SEC < 1000)
return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
static mi_msecs_t mi_prim_clock_now_lowres(void) {
const int64_t ticks = (int64_t)clock();
#if !defined(CLOCKS_PER_SEC)
return ticks;
#else
return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
if (CLOCKS_PER_SEC <= 0 || CLOCKS_PER_SEC == 1000) {
return ticks;
}
else if (CLOCKS_PER_SEC > 0 && CLOCKS_PER_SEC < 1000) {
return ticks * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
}
else {
return ticks / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
}
#endif
}
#endif
mi_msecs_t _mi_prim_clock_now(void) {
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
#ifdef CLOCK_MONOTONIC
const clockid_t clockid = CLOCK_MONOTONIC;
#else
const clockid_t clockid = CLOCK_REALTIME;
#endif
struct timespec t;
if (clock_gettime(clockid,&t) == 0) {
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000L);
}
#endif
return mi_prim_clock_now_lowres();
}
//----------------------------------------------------------------
@@ -237,9 +243,9 @@ void _mi_prim_out_stderr( const char* msg ) {
// Environment
//----------------------------------------------------------------
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
// cannot call getenv() when still initializing the C runtime.
if (_mi_preloading()) return false;
if (_mi_preloading()) return -1; // error, try again later
const char* s = getenv(name);
if (s == NULL) {
// we check the upper case name too.
@@ -251,9 +257,9 @@ bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
buf[len] = 0;
s = getenv(buf);
}
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return false;
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return 0; // not found
_mi_strlcpy(result, s, result_size);
return true;
return 1; // found
}
+151 -73
View File
@@ -13,9 +13,9 @@ terms of the MIT license. A copy of the license can be found in the file
#include <stdio.h> // fputs, stderr
#include <stdlib.h> // atexit
// xbox has no console IO
#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
#define MI_HAS_CONSOLE_IO
// xbox has no console IO and cannot use LoadLibrary or GetModuleHandle
#if !defined(WINAPI_FAMILY_PARTITION) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
#define MI_WIN_DESKTOP 1
#endif
//---------------------------------------------
@@ -87,6 +87,34 @@ typedef BOOL (__stdcall *PGetPhysicallyInstalledSystemMemory)( PULONGLONG TotalM
typedef BOOL (__stdcall* PGetVersionExW)(LPOSVERSIONINFOW lpVersionInformation);
// Load a library
static HMODULE mi_win_loadlibrary(const TCHAR* library) {
#if MI_WIN_DESKTOP
return LoadLibrary(library);
#else
return LoadPackagedLibrary(library, 0);
#endif
}
// Get a library handle (and possibly load it)
static HMODULE mi_win_getlibrary(const TCHAR* library, bool* should_free) {
#if MI_WIN_DESKTOP
// avoid calling LoadLibrary for "kernel32", "ntdll", and "kernelbase" (also to avoid hitting the loader lock)
HMODULE mod = GetModuleHandle(library);
if (mod!=NULL) {
*should_free = false;
return mod;
}
#endif
*should_free = true;
return mi_win_loadlibrary(library);
}
static void mi_win_freelibrary(HMODULE mod, bool should_free) {
if (should_free) {
FreeLibrary(mod);
}
}
//---------------------------------------------
// Enable large page support dynamically (if possible)
@@ -103,15 +131,17 @@ static bool win_enable_large_os_pages_once(size_t* large_page_size)
unsigned long err = 0;
HANDLE token = NULL;
BOOL ok = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token);
err = GetLastError();
if (ok) {
TOKEN_PRIVILEGES tp;
ok = LookupPrivilegeValue(NULL, TEXT("SeLockMemoryPrivilege"), &tp.Privileges[0].Luid);
err = GetLastError();
if (ok) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
ok = AdjustTokenPrivileges(token, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
err = GetLastError();
if (ok) {
err = GetLastError();
ok = (err == ERROR_SUCCESS);
if (ok && large_page_size != NULL && pGetLargePageMinimum != NULL) {
*large_page_size = (*pGetLargePageMinimum)();
@@ -121,17 +151,19 @@ static bool win_enable_large_os_pages_once(size_t* large_page_size)
CloseHandle(token);
}
if (!ok) {
if (err == 0) err = GetLastError();
if (err == 0) { err = GetLastError(); }
_mi_warning_message("cannot enable large OS page support, error %lu\n", err);
}
return (ok!=0);
}
static bool win_enable_large_os_pages(size_t* large_page_size) {
static size_t win_large_page_size = 0;
mi_atomic_do_once {
win_enable_large_os_pages_once(large_page_size);
win_enable_large_os_pages_once(&win_large_page_size);
}
return (_mi_os_large_page_size() > 0);
if (large_page_size != NULL) { *large_page_size = win_large_page_size; }
return (win_large_page_size > 0);
}
@@ -162,21 +194,22 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config )
}
// get the VirtualAlloc2 function
HINSTANCE hDll = LoadLibrary(TEXT("kernelbase.dll"));
bool hDllFree;
HINSTANCE hDll = mi_win_getlibrary(TEXT("kernelbase.dll"), &hDllFree);
if (hDll != NULL) {
// use VirtualAlloc2FromApp if possible as it is available to Windows store apps
pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2FromApp");
if (pVirtualAlloc2==NULL) pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2");
FreeLibrary(hDll);
mi_win_freelibrary(hDll, hDllFree);
}
// NtAllocateVirtualMemoryEx is used for huge page allocation
hDll = LoadLibrary(TEXT("ntdll.dll"));
hDll = mi_win_getlibrary(TEXT("ntdll.dll"), &hDllFree);
if (hDll != NULL) {
pNtAllocateVirtualMemoryEx = (PNtAllocateVirtualMemoryEx)(void (*)(void))GetProcAddress(hDll, "NtAllocateVirtualMemoryEx");
FreeLibrary(hDll);
mi_win_freelibrary(hDll, hDllFree);
}
// Try to use Win7+ numa API
hDll = LoadLibrary(TEXT("kernel32.dll"));
hDll = mi_win_getlibrary(TEXT("kernel32.dll"), &hDllFree);
if (hDll != NULL) {
pGetCurrentProcessorNumberEx = (PGetCurrentProcessorNumberEx)(void (*)(void))GetProcAddress(hDll, "GetCurrentProcessorNumberEx");
pGetNumaProcessorNodeEx = (PGetNumaProcessorNodeEx)(void (*)(void))GetProcAddress(hDll, "GetNumaProcessorNodeEx");
@@ -205,7 +238,7 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config )
win_minor_version = version.dwMinorVersion;
}
}
FreeLibrary(hDll);
mi_win_freelibrary(hDll, hDllFree);
}
// Enable large/huge OS page support?
if (mi_option_is_enabled(mi_option_allow_large_os_pages) || mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {
@@ -228,8 +261,9 @@ int _mi_prim_free(void* addr, size_t size ) {
// the memory region returned by VirtualAlloc; in that case we need to free using
// the start of the region.
MEMORY_BASIC_INFORMATION info; _mi_memzero_var(info);
VirtualQuery(addr, &info, sizeof(info));
if (info.AllocationBase < addr && ((uint8_t*)addr - (uint8_t*)info.AllocationBase) < (ptrdiff_t)(4*MI_MiB)) {
err = (VirtualQuery(addr, &info, sizeof(info)) == 0);
if (err) { errcode = GetLastError(); }
if (!err && info.AllocationBase < addr && ((uint8_t*)addr - (uint8_t*)info.AllocationBase) < (ptrdiff_t)(4*MI_MiB)) {
errcode = 0;
err = (VirtualFree(info.AllocationBase, 0, MEM_RELEASE) == 0);
if (err) { errcode = GetLastError(); }
@@ -244,7 +278,7 @@ int _mi_prim_free(void* addr, size_t size ) {
//---------------------------------------------
static void* win_virtual_alloc_prim_once(void* addr, size_t size, size_t try_alignment, DWORD flags) {
#if (MI_INTPTR_SIZE >= 8)
#if (MI_SIZE_SIZE >= 8)
// on 64-bit systems, try to use the virtual address area after 2TiB for 4MiB aligned allocations
if (addr == NULL) {
void* hint = _mi_os_get_aligned_hint(try_alignment,size);
@@ -525,8 +559,9 @@ static mi_msecs_t mi_to_msecs(LARGE_INTEGER t) {
static LARGE_INTEGER mfreq; // = 0
if (mfreq.QuadPart == 0LL) {
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
mfreq.QuadPart = f.QuadPart/1000LL;
if (QueryPerformanceFrequency(&f)) {
mfreq.QuadPart = f.QuadPart/1000LL;
}
if (mfreq.QuadPart == 0) mfreq.QuadPart = 1;
}
return (mi_msecs_t)(t.QuadPart / mfreq.QuadPart);
@@ -534,8 +569,12 @@ static mi_msecs_t mi_to_msecs(LARGE_INTEGER t) {
mi_msecs_t _mi_prim_clock_now(void) {
LARGE_INTEGER t;
QueryPerformanceCounter(&t);
return mi_to_msecs(t);
if (QueryPerformanceCounter(&t)) {
return mi_to_msecs(t);
}
else {
return 0;
}
}
@@ -562,29 +601,31 @@ void _mi_prim_process_info(mi_process_info_t* pinfo)
FILETIME ut;
FILETIME st;
FILETIME et;
GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut);
pinfo->utime = filetime_msecs(&ut);
pinfo->stime = filetime_msecs(&st);
if (GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut)) {
pinfo->utime = filetime_msecs(&ut);
pinfo->stime = filetime_msecs(&st);
}
// load psapi on demand
mi_atomic_do_once {
HINSTANCE hDll = LoadLibrary(TEXT("psapi.dll"));
mi_atomic_do_once{
HINSTANCE hDll = mi_win_loadlibrary(TEXT("psapi.dll"));
if (hDll != NULL) {
pGetProcessMemoryInfo = (PGetProcessMemoryInfo)(void (*)(void))GetProcAddress(hDll, "GetProcessMemoryInfo");
// FreeLibrary(hDll); // don't free
// mi_win_freelibrary(hDll, true); // don't free
}
}
// get process info
PROCESS_MEMORY_COUNTERS info; _mi_memzero_var(info);
if (pGetProcessMemoryInfo != NULL) {
pGetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
PROCESS_MEMORY_COUNTERS info; _mi_memzero_var(info);
if (pGetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info))) {
pinfo->current_rss = (size_t)info.WorkingSetSize;
pinfo->peak_rss = (size_t)info.PeakWorkingSetSize;
pinfo->current_commit = (size_t)info.PagefileUsage;
pinfo->peak_commit = (size_t)info.PeakPagefileUsage;
pinfo->page_faults = (size_t)info.PageFaultCount;
}
}
pinfo->current_rss = (size_t)info.WorkingSetSize;
pinfo->peak_rss = (size_t)info.PeakWorkingSetSize;
pinfo->current_commit = (size_t)info.PagefileUsage;
pinfo->peak_commit = (size_t)info.PeakPagefileUsage;
pinfo->page_faults = (size_t)info.PageFaultCount;
}
//----------------------------------------------------------------
@@ -596,32 +637,37 @@ void _mi_prim_out_stderr( const char* msg )
// on windows with redirection, the C runtime cannot handle locale dependent output
// after the main thread closes so we use direct console output.
if (!_mi_preloading()) {
// _cputs(msg); // _cputs cannot be used as it aborts when failing to lock the console
static HANDLE hcon = INVALID_HANDLE_VALUE;
#if MI_WIN_DESKTOP
static bool hconIsConsole = false;
#endif
if (hcon == INVALID_HANDLE_VALUE) {
hcon = GetStdHandle(STD_ERROR_HANDLE);
#ifdef MI_HAS_CONSOLE_IO
hcon = GetStdHandle(STD_ERROR_HANDLE); // returns NULL if no stderr is available
#if MI_WIN_DESKTOP
if (hcon==NULL) {
AttachConsole(ATTACH_PARENT_PROCESS); // if started from a parent console, try to attach to that
hcon = GetStdHandle(STD_ERROR_HANDLE);
}
CONSOLE_SCREEN_BUFFER_INFO sbi;
hconIsConsole = ((hcon != INVALID_HANDLE_VALUE) && GetConsoleScreenBufferInfo(hcon, &sbi));
hconIsConsole = ((hcon != NULL && hcon != INVALID_HANDLE_VALUE) && GetConsoleScreenBufferInfo(hcon, &sbi));
#endif
}
const size_t len = _mi_strlen(msg);
if (len > 0 && len < UINT32_MAX) {
DWORD written = 0;
if (hconIsConsole) {
#ifdef MI_HAS_CONSOLE_IO
WriteConsoleA(hcon, msg, (DWORD)len, &written, NULL);
if (hcon != NULL && hcon != INVALID_HANDLE_VALUE) {
#if MI_WIN_DESKTOP
if (hconIsConsole) {
WriteConsoleA(hcon, msg, (DWORD)len, &written, NULL);
}
else
#endif
{
// use direct write in case stderr was redirected
WriteFile(hcon, msg, (DWORD)len, &written, NULL);
}
}
else if (hcon != INVALID_HANDLE_VALUE) {
// use direct write if stderr was redirected
WriteFile(hcon, msg, (DWORD)len, &written, NULL);
}
else {
// finally fall back to fputs after all
fputs(msg, stderr);
}
// don't fall back to fputs or _cputs as the crt can have it locked
}
}
}
@@ -635,10 +681,10 @@ void _mi_prim_out_stderr( const char* msg )
// reliably even when this is invoked before the C runtime is initialized.
// i.e. when `_mi_preloading() == true`.
// Note: on windows, environment names are not case sensitive.
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
result[0] = 0;
const size_t len = GetEnvironmentVariableA(name, result, (DWORD)result_size);
return (len > 0 && len < result_size);
return (len < result_size ? (len > 0 ? 1 /* success */ : 0 /* not found */) : -1 /* error */);
}
@@ -673,10 +719,10 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
mi_assert(buf_len <= ULONG_MAX);
if (buf_len > ULONG_MAX) return false;
mi_atomic_do_once {
HINSTANCE hDll = LoadLibrary(TEXT("bcrypt.dll"));
HINSTANCE hDll = mi_win_loadlibrary(TEXT("bcrypt.dll"));
if (hDll != NULL) {
pBCryptGenRandom = (PBCryptGenRandom)(void (*)(void))GetProcAddress(hDll, "BCryptGenRandom");
// FreeLibrary(hDll); // don't free
// mi_win_freelibrary(hDll); // don't free
}
}
if (pBCryptGenRandom == NULL) return false;
@@ -737,15 +783,18 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
both static and dynamic linkage (`MI_WIN_INIT_USE_CRT_TLS`).
------------------------------------------------------------------------- */
#if !defined(MI_WIN_INIT_USE_CRT_TLS) && !defined(MI_WIN_INIT_USE_RAW_DLLMAIN) && !defined(MI_WIN_INIT_USE_TLS_DLLMAIN) && !defined(MI_WIN_INIT_USE_FLS)
#if !defined(__INTEL_LLVM_COMPILER) && !defined(__INTEL_COMPILER)
#define MI_WIN_INIT_USE_CRT_TLS 1
#if defined(__INTEL_LLVM_COMPILER) || defined(__INTEL_COMPILER)
#define MI_WIN_INIT_USE_TLS_DLLMAIN 1 /* needed for Intel ICX, see issue #1268 */
#else
#define MI_WIN_INIT_USE_TLS_DLLMAIN 1 /* default for Intel ICX, see issue #1268 */
#define MI_WIN_INIT_USE_CRT_TLS 1 /* default */
#endif
#endif
#if defined(MI_WIN_INIT_USE_CRT_TLS)
#define MI_PRIM_HAS_PROCESS_ATTACH 1
#if !defined(__MINGW32__) || defined(MI_MINGW_UCRT64) // on mingw without UCRT use the constructor attribute (in `src/prim/prim.c`)
#define MI_PRIM_HAS_PROCESS_ATTACH 1
#endif
// nothing to do since `_mi_thread_done` is handled through the DLL_THREAD_DETACH event.
void _mi_prim_thread_init_auto_done(void) {}
void _mi_prim_thread_done_auto_done(void) {}
@@ -818,10 +867,12 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
#endif
typedef int (mi_cdecl* mi_crt_callback_t)(void);
#if defined(_WIN64)
#if defined(_WIN64) && defined(_MSC_VER) // 64-bit
#pragma comment(linker, "/INCLUDE:_tls_used")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_post")
#pragma comment(linker, "/INCLUDE:_mi_crt_callback_init")
#pragma const_seg(".CRT$XLB")
extern const PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[];
const PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[] = { &mi_tls_attach };
@@ -834,10 +885,11 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
extern const mi_crt_callback_t _mi_crt_callback_init[];
const mi_crt_callback_t _mi_crt_callback_init[] = { &mi_crt_init };
#pragma const_seg()
#else
#elif defined(_MSC_VER) // 32-bit
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_post")
#pragma comment(linker, "/INCLUDE:__mi_crt_callback_init")
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[] = { &mi_tls_attach };
#pragma data_seg()
@@ -847,6 +899,12 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
#pragma data_seg(".CRT$XIB")
mi_crt_callback_t _mi_crt_callback_init[] = { &mi_crt_init };
#pragma data_seg()
#elif defined(__MINGW32__)
extern const IMAGE_TLS_DIRECTORY _tls_used;
__attribute__((used)) static const void* const mi_tls_used_ref = &_tls_used; // pull in the CRT tls
__attribute__((used, section(".CRT$XLB"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_pre = &mi_tls_attach;
__attribute__((used, section(".CRT$XLY"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_post = &mi_tls_detach;
__attribute__((used, section(".CRT$XIB"))) mi_crt_callback_t _mi_crt_callback_init = &mi_crt_init;
#endif
#if defined(__cplusplus)
@@ -916,7 +974,7 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
extern "C" {
#endif
#if defined(_WIN64)
#if defined(_WIN64) && defined(_MSC_VER) // 64-bit
#pragma comment(linker, "/INCLUDE:_tls_used")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_post")
@@ -928,7 +986,7 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
extern const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[];
const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_tls_detach };
#pragma const_seg()
#else
#elif defined(_MSC_VER) // 32-bit
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_post")
@@ -938,6 +996,11 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
#pragma data_seg(".CRT$XLY")
PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_tls_detach };
#pragma data_seg()
#elif defined(__MINGW32__)
extern const IMAGE_TLS_DIRECTORY _tls_used;
__attribute__((used)) static const void* const mi_tls_used_ref = &_tls_used; // pull in the CRT tls
__attribute__((used, section(".CRT$XLB"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_pre = &mi_tls_attach;
__attribute__((used, section(".CRT$XLY"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_post = &mi_tls_detach;
#endif
#if defined(__cplusplus)
@@ -985,7 +1048,7 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
extern "C" {
#endif
#if defined(_WIN64)
#if defined(_WIN64) && defined(_MSC_VER) // 64-bit
#pragma comment(linker, "/INCLUDE:_tls_used")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:_mi_tls_callback_post")
@@ -997,16 +1060,21 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
extern const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[];
const PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_win_main_detach };
#pragma const_seg()
#else
#elif defined(_MSC_VER) // 32-bit
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_pre")
#pragma comment(linker, "/INCLUDE:__mi_tls_callback_post")
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK _mi_tls_callback_pre[] = { &mi_win_main_attach };
#pragma data_seg()
#pragma data_seg(".CRT$XIY")
#pragma data_seg(".CRT$XLY")
PIMAGE_TLS_CALLBACK _mi_tls_callback_post[] = { &mi_win_main_detach };
#pragma data_seg()
#elif defined(__MINGW32__)
extern const IMAGE_TLS_DIRECTORY _tls_used;
__attribute__((used)) static const void* const mi_tls_used_ref = &_tls_used; // pull in the CRT tls
__attribute__((used, section(".CRT$XLB"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_pre = &mi_tls_attach;
__attribute__((used, section(".CRT$XLY"))) PIMAGE_TLS_CALLBACK _mi_tls_callback_post = &mi_tls_detach;
#endif
#if defined(__cplusplus)
@@ -1027,21 +1095,31 @@ static void NTAPI mi_win_main(PVOID module, DWORD reason, LPVOID reserved) {
// See <https://www.codeguru.com/cpp/misc/misc/applicationcontrol/article.php/c6945/Running-Code-Before-and-After-Main.htm>
#define MI_PRIM_HAS_PROCESS_ATTACH 1
static int mi_process_attach(void) {
static int mi_cdecl mi_crt_init(void) {
mi_win_main(NULL,DLL_PROCESS_ATTACH,NULL);
atexit(&_mi_auto_process_done);
return 0;
}
typedef int(*mi_crt_callback_t)(void);
#if defined(_WIN64)
#pragma comment(linker, "/INCLUDE:_mi_tls_callback")
#pragma section(".CRT$XIU", long, read)
#else
#pragma comment(linker, "/INCLUDE:__mi_tls_callback")
#if defined(__cplusplus)
extern "C" {
#endif
typedef int (mi_cdecl* mi_crt_callback_t)(void);
#if defined(_WIN64) // 64-bit
#pragma comment(linker, "/INCLUDE:_mi_crt_callback_init")
#pragma const_seg(".CRT$XIU")
extern const mi_crt_callback_t _mi_crt_callback_init[];
const mi_crt_callback_t _mi_crt_callback_init[] = { &mi_crt_init };
#pragma const_seg()
#else // 32-bit
#pragma comment(linker, "/INCLUDE:__mi_crt_callback_init")
#pragma data_seg(".CRT$XIU")
mi_crt_callback_t _mi_crt_callback_init[] = { &mi_crt_init };
#pragma data_seg()
#endif
#if defined(__cplusplus)
}
#endif
#pragma data_seg(".CRT$XIU")
mi_decl_externc mi_crt_callback_t _mi_tls_callback[] = { &mi_process_attach };
#pragma data_seg()
#endif
// use the fiber api for calling `_mi_thread_done`.
+14 -12
View File
@@ -134,18 +134,19 @@ static bool mi_random_is_initialized(mi_random_ctx_t* ctx) {
void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) {
mi_assert_internal(mi_random_is_initialized(ctx));
mi_assert_internal(ctx != ctx_new);
chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new);
mi_assert_internal(ctx != ctx_new);
const uintptr_t nonce_rnd = _mi_random_next(ctx);
chacha_split(ctx, (uintptr_t)ctx_new ^ nonce_rnd /*nonce*/, ctx_new);
}
uintptr_t _mi_random_next(mi_random_ctx_t* ctx) {
size_t _mi_random_next(mi_random_ctx_t* ctx) {
mi_assert_internal(mi_random_is_initialized(ctx));
uintptr_t r;
size_t r;
do {
#if MI_INTPTR_SIZE <= 4
#if MI_SIZE_SIZE <= 4
r = chacha_next32(ctx);
#elif MI_INTPTR_SIZE == 8
r = (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
#elif MI_SIZE_SIZE == 8
r = (((size_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
#else
# error "define mi_random_next for this platform"
#endif
@@ -159,12 +160,12 @@ To initialize a fresh random context.
If we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR.
-----------------------------------------------------------------------------*/
uintptr_t _mi_os_random_weak(uintptr_t extra_seed) {
uintptr_t x = (uintptr_t)&_mi_os_random_weak ^ extra_seed; // ASLR makes the address random
size_t _mi_os_random_weak(size_t extra_seed) {
size_t x = (size_t)&_mi_os_random_weak ^ extra_seed; // ASLR makes the address random
x ^= _mi_prim_clock_now();
// and do a few randomization steps
uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1;
for (uintptr_t i = 0; i < max || x==0; i++, x++) {
size_t max = ((x ^ (x >> 17)) & 0x0F) + 1;
for (size_t i = 0; i < max || x==0; i++, x++) {
x = _mi_random_shuffle(x);
}
mi_assert_internal(x != 0);
@@ -179,7 +180,7 @@ static void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) {
#if !defined(__wasi__)
if (!use_weak) { _mi_warning_message("unable to use secure randomness\n"); }
#endif
uintptr_t x = _mi_os_random_weak(0);
size_t x = _mi_os_random_weak(0);
for (size_t i = 0; i < 32; i+=4, x++) {
x = _mi_random_shuffle(x);
key[i] = (uint8_t)(x);
@@ -193,6 +194,7 @@ static void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) {
ctx->weak = false;
}
chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ );
_mi_memzero(key, sizeof(key));
}
void _mi_random_init(mi_random_ctx_t* ctx) {
+173
View File
@@ -0,0 +1,173 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2019-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim-tls.h"
mi_decl_export void mi_theap_guarded_set_sample_rate(mi_theap_t* theap, size_t sample_rate, size_t seed) {
theap->guarded_sample_rate = (sample_rate > MI_SAMPLE_RATE_MAX ? MI_SAMPLE_RATE_MAX : sample_rate);
if (theap->guarded_sample_rate > 1) {
if (seed == 0) {
seed = _mi_theap_random_next(theap);
}
theap->guarded_sample_countdown = (seed % theap->guarded_sample_rate) + 1; // start at random count between 1 and `sample_rate`
}
else if (theap->guarded_sample_countdown > theap->guarded_sample_rate) {
theap->guarded_sample_countdown = theap->guarded_sample_rate;
}
_mi_theap_update_sample_rate(theap);
}
mi_decl_export void mi_theap_guarded_set_size_bound(mi_theap_t* theap, size_t min, size_t max) {
theap->guarded_size_min = min;
theap->guarded_size_max = (min > max ? min : max);
}
void _mi_theap_guarded_init(mi_theap_t* theap) {
#if MI_GUARDED
mi_theap_guarded_set_sample_rate(theap,
(size_t)mi_option_get_clamp(mi_option_guarded_sample_rate, 0, LONG_MAX),
(size_t)mi_option_get(mi_option_guarded_sample_seed));
mi_theap_guarded_set_size_bound(theap,
(size_t)mi_option_get_clamp(mi_option_guarded_min, 0, LONG_MAX),
(size_t)mi_option_get_clamp(mi_option_guarded_max, 0, LONG_MAX) );
#else
mi_theap_guarded_set_sample_rate(theap,0,0);
#endif
}
// We always allocate a guarded allocation at an offset (`mi_page_has_interior_pointers` will be true).
// We then set the first word of the block to `0` for regular offset aligned allocations (in `alloc-aligned.c`)
// and the first word to `~0` for guarded allocations to have a correct `mi_usable_size`
static void* mi_block_ptr_set_guarded(mi_block_t* block, size_t obj_size, size_t* usable_size) {
// todo: we can still make padding work by moving it out of the guard page area
mi_page_t* const page = _mi_ptr_page(block);
mi_page_set_has_interior_pointers(page, true);
block->next = MI_BLOCK_TAG_GUARDED;
// set guard page at the end of the block
const size_t block_size = mi_page_block_size(page); // must use `block_size` to match `mi_free_local`
const size_t os_page_size = _mi_os_page_size();
mi_assert_internal(block_size >= obj_size + os_page_size + sizeof(mi_block_t));
if (block_size < obj_size + os_page_size + sizeof(mi_block_t)) {
// should never happen
mi_free(block);
return NULL;
}
uint8_t* guard_page = (uint8_t*)block + block_size - os_page_size;
// note: the alignment of the guard page relies on blocks being os_page_size aligned which
// is ensured in `mi_arena_page_alloc_fresh`.
mi_assert_internal(_mi_is_aligned(block, os_page_size));
mi_assert_internal(_mi_is_aligned(guard_page, os_page_size));
if (!page->memid.is_pinned && _mi_is_aligned(guard_page, os_page_size)) {
const bool ok = _mi_os_protect(guard_page, os_page_size);
if mi_unlikely(!ok) {
_mi_warning_message("failed to set a guard page behind an object (object %p of size %zu)\n", block, block_size);
}
}
else {
_mi_warning_message("unable to set a guard page behind an object due to pinned memory (large OS pages?) (object %p of size %zu)\n", block, block_size);
}
// align pointer just in front of the guard page
size_t offset = block_size - os_page_size - obj_size;
mi_assert_internal(offset > sizeof(mi_block_t));
if (offset > MI_PAGE_MAX_OVERALLOC_ALIGN) {
// give up to place it right in front of the guard page if the offset is too large for unalignment
offset = MI_PAGE_MAX_OVERALLOC_ALIGN;
}
uint8_t* const p = (uint8_t*)block + offset;
mi_assert_internal(p == guard_page - obj_size || offset >= MI_PAGE_MAX_OVERALLOC_ALIGN);
if (usable_size != NULL) { *usable_size = (guard_page - p); mi_assert_internal(mi_usable_size(p)==*usable_size); }
mi_track_align(block, p, offset, obj_size);
mi_track_mem_defined(block, sizeof(mi_block_t));
return p;
}
// Allocate a block with a guard page behind it.
mi_decl_restrict void* _mi_theap_malloc_guarded(mi_theap_t* theap, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept
{
// allocate multiple of page size ending in a guard page
// ensure minimal alignment requirement?
if mi_unlikely(size >= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) { // check up front so the `req_size` won't overflow
_mi_error_message(EOVERFLOW, "(guarded) allocation request is too large (%zu bytes)\n", size);
return NULL;
}
const size_t os_page_size = _mi_os_page_size();
const size_t obj_size = (mi_option_is_enabled(mi_option_guarded_precise) ? size : _mi_align_up(size, MI_MAX_ALIGN_SIZE));
const size_t bsize = _mi_align_up(_mi_align_up(obj_size, MI_MAX_ALIGN_SIZE) + sizeof(mi_block_t), MI_MAX_ALIGN_SIZE);
const size_t req_size = _mi_align_up(bsize + os_page_size, os_page_size);
// const size_t threshold = mi_theap_disable_profiler(theap);
mi_block_t* const block = (mi_block_t*)_mi_malloc_generic_no_sample(theap, req_size, false /* don't zero */, ppage);
// mi_theap_enable_profiler(theap,threshold);
if (block==NULL) return NULL;
size_t usable_size = 0;
void* const p = mi_block_ptr_set_guarded(block, obj_size, &usable_size);
if (p == NULL) return NULL;
if (zero) {
_mi_memzero(p,obj_size); // we have to zero afterwards as padding might have written inside the block (if the `blocksize > reqsize + os_page_size`)
}
// stats
mi_track_malloc(p, usable_size, zero);
if (!mi_theap_is_initialized(theap)) { theap = _mi_theap_default(); }
mi_theap_stat_counter_increase(theap, malloc_guarded_count, 1);
#if MI_STATS
// adjust request stats to only count the allocated size of the block (and not the guard page)
mi_theap_stat_counter_decrease(theap, malloc_requested, req_size);
mi_theap_stat_counter_increase(theap, malloc_requested, size);
#endif
#if MI_DEBUG>3
if (zero) {
mi_assert_expensive(mi_mem_is_zero(p, size));
}
#endif
#if MI_PAGE_META_SMALL_IS_ALIGNED && MI_DEBUG>=2
// we should never allocate something allocated as small in a non-small page or otherwise aligned mi_free_small may fail.
if (size <= MI_SMALL_SIZE_MAX) {
mi_page_t* const page = _mi_ptr_page(p);
mi_assert_internal(mi_page_block_size(page) <= MI_SMALL_MAX_OBJ_SIZE);
}
#endif
return p;
}
// Remove guard page when building with MI_GUARDED
#if MI_GUARDED
void _mi_page_block_unguard(mi_page_t* page, mi_block_t* block, void* p) {
// if (!mi_block_ptr_is_guarded(block,p)) return;
mi_assert_internal(mi_block_ptr_is_guarded(block, p));
mi_assert_internal(mi_page_has_interior_pointers(page));
mi_assert_internal((uint8_t*)p - (uint8_t*)block >= (ptrdiff_t)sizeof(mi_block_t));
mi_assert_internal(block->next == MI_BLOCK_TAG_GUARDED);
const size_t bsize = mi_page_block_size(page);
const size_t psize = _mi_os_page_size();
mi_assert_internal(bsize > psize);
mi_assert_internal(!page->memid.is_pinned);
void* gpage = (uint8_t*)block + bsize - psize;
mi_assert_internal(_mi_is_aligned(gpage, psize));
_mi_os_unprotect(gpage, psize);
}
// unguard a whole page (called from `mi_heap_destroy`)
void _mi_page_unguard_all(mi_page_t* page) {
if mi_likely(!mi_page_has_interior_pointers(page)) return;
uint8_t* const start = mi_page_start(page);
const size_t psize = mi_page_committed(page);
_mi_os_unprotect(start,psize); // unprotect all at once as we cannot know which blocks are guarded
}
#else
void _mi_page_block_unguard(mi_page_t* page, mi_block_t* block, void* p) {
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(p);
}
void _mi_page_unguard_all(mi_page_t* page) {
MI_UNUSED(page);
}
#endif
+264
View File
@@ -0,0 +1,264 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2019-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim-tls.h"
//----------------------------------------------------------------------------
// General sampled allocation (called from `page.c:mi_malloc_generic_fallback`)
// This is for both guarded and profiled sampling
//----------------------------------------------------------------------------
size_t _mi_theap_update_sample_rate(mi_theap_t* theap) {
const size_t old_sample_rate = theap->sample_rate;
theap->sample_rate = theap->profile_sample_rate;
if (theap->sample_rate == 0 || (theap->guarded_sample_rate!=0 && theap->sample_rate > theap->guarded_sample_rate)) {
theap->sample_rate = theap->guarded_sample_rate;
}
if (theap->sample_countdown > theap->sample_rate) {
theap->sample_countdown = theap->sample_rate; // todo: adjust difference?
}
return old_sample_rate;
}
// Called when the theap sample countdown <= req_size; should call guarded or profiled allocation.
mi_decl_noinline mi_decl_restrict void* _mi_theap_malloc_sampled(mi_theap_t* theap, size_t req_size, bool zero, mi_page_t** ppage) mi_attr_noexcept
{
// the size has not yet been counted against the countdown (and does not include MI_PADDING_SIZE)
mi_assert_internal(req_size <= MI_MAX_ALLOC_SIZE);
mi_assert_internal((theap->sample_countdown==SIZE_MAX && _mi_is_empty_theap(theap)) ||
(theap->sample_countdown <= MI_SAMPLE_COUNTDOWN_MAX && theap->sample_countdown < req_size));
const size_t size = req_size + MI_PADDING_SIZE;
// handle empty theap and disabled sampling
if (theap->sample_rate==0) {
if (!_mi_is_empty_theap(theap)) { // avoid writing to the initial empty theap
theap->sample_countdown = MI_SAMPLE_COUNTDOWN_MAX; // avoid the sampling path for a long time
}
return _mi_malloc_generic_no_sample(theap,size,zero,ppage);
}
// update countdown and total accummulated requested bytes since the last sample
mi_assert_internal(!_mi_is_empty_theap(theap));
mi_assert_internal(req_size <= SIZE_MAX/2);
mi_assert_internal(theap->sample_rate > 0);
mi_assert_internal(theap->sample_rate <= SIZE_MAX/2);
mi_assert_internal(theap->sample_rate >= theap->sample_countdown);
const uint64_t requested = theap->sample_requested = (uint64_t)theap->sample_rate + (uint64_t)(req_size - theap->sample_countdown) + theap->sample_requested;
mi_assert_internal(requested > 0);
mi_assert_internal(requested >= req_size);
theap->sample_countdown = theap->sample_rate; // reset sampling
// update derived countdowns
mi_assert_internal(theap->profile_sample_rate!=0 || theap->profile_sample_countdown==0);
mi_assert_internal(theap->guarded_sample_rate!=0 || theap->guarded_sample_countdown==0);
bool sample_profile = false;
bool sample_guarded = false;
if (theap->profile_sample_rate!=0) {
if (theap->profile_sample_countdown >= requested) { theap->profile_sample_countdown -= (size_t)requested; } else { sample_profile = true; }
}
if (theap->guarded_sample_rate!=0) {
if (theap->guarded_sample_countdown >= requested) { theap->guarded_sample_countdown -= (size_t)requested; } else { sample_guarded = true; }
}
// invoke callback?
if (sample_profile) {
theap->profile_sample_countdown = theap->profile_sample_rate; // reset countdown
theap->sample_requested = 0; // reset requested as we pass it to malloc_profiled
#if MI_PROFILE
return _mi_theap_malloc_profiled(theap,size,requested,zero,ppage);
#endif
}
else if (sample_guarded) {
if (req_size >= theap->guarded_size_min && req_size <= theap->guarded_size_max) {
// use guarded allocation
theap->guarded_sample_countdown = theap->guarded_sample_rate; // reset countdown
#if MI_GUARDED
return _mi_theap_malloc_guarded(theap,size,zero,ppage);
#endif
}
#if MI_GUARDED
else {
// failed size criteria, rewind the sample countdown so we sample asap again
// todo: can we do better here as this will cause many samples until it fits the size..
theap->sample_countdown = 0;
}
#endif
}
// take generic path
return _mi_malloc_generic_no_sample(theap,size,zero,ppage);
}
//----------------------------------------------------------------------------
// Util
//----------------------------------------------------------------------------
static mi_profiler_t* mi_heap_profiler(const mi_heap_t* heap) {
return mi_atomic_load_ptr_acquire(mi_profiler_t,&heap->profiler);
}
static mi_profiler_t* mi_theap_get_enabled_profiler(const mi_theap_t* theap) {
mi_heap_t* const heap = _mi_theap_heap(theap);
mi_profiler_t* prof = mi_atomic_load_ptr_relaxed(mi_profiler_t, &heap->profiler);
if (prof!=NULL && mi_profiler_is_enabled(prof) && prof->on_alloc != NULL) {
return prof;
}
else {
return NULL;
}
}
//----------------------------------------------------------------------------
// Profile an allocation
//-----------------------------------------------------------------------------
mi_decl_noinline mi_decl_restrict void* _mi_theap_malloc_profiled(mi_theap_t* theap, size_t size, uint64_t requested_since_last_sample, bool zero, mi_page_t** ppage) mi_attr_noexcept
{
mi_assert_internal(theap!=NULL);
mi_assert_internal(size>=MI_PADDING_SIZE);
const size_t req_size = size - MI_PADDING_SIZE;
mi_assert_internal(req_size<=requested_since_last_sample);
mi_profiler_t* const prof = mi_theap_get_enabled_profiler(theap);
if (prof == NULL) { return _mi_malloc_generic_no_sample(theap,size,zero,ppage); }
mi_assert_internal(prof!=NULL && prof->on_alloc!=NULL && mi_profiler_is_enabled(prof));
void* p = NULL;
size_t new_sample_rate = 0;
if (prof->on_free==NULL || prof->sample_data_size==0) {
// just allocate without profiler data
p = _mi_malloc_generic_no_sample(theap,size,zero,ppage);
if (p==NULL) { return p; }
if (prof->on_alloc!=NULL) {
// we are just allocation profiling (not heap profiling as on_free == NULL)
new_sample_rate = (*prof->on_alloc)(prof, NULL /* no data */, p, req_size, theap->profile_sample_rate, requested_since_last_sample, _mi_theap_heap(theap) );
}
}
else {
// overallocate a larger block to store the profiler data
// [MI_BLOCK_TAG_PROFILE] [usable size] [ ... profile data ... ] [... user data ...]
const size_t sample_data_offset = sizeof(mi_block_t);
const size_t sample_user_data_size = _mi_align_up(prof->sample_data_size > MI_PROFILE_SAMPLE_DATA_MAX_SIZE ? MI_PROFILE_SAMPLE_DATA_MAX_SIZE : prof->sample_data_size, sizeof(void*));
const size_t sample_data_size = sizeof(mi_profiler_sample_data_t) + sample_user_data_size; // one void* too many just in case
const size_t user_offset = _mi_align_up(sample_data_offset + sample_data_size, MI_MAX_ALIGN_SIZE);
const size_t oversize = user_offset + size;
mi_page_t* page = NULL;
mi_block_t* const block = (mi_block_t*)_mi_malloc_generic_no_sample(theap,oversize,zero,&page);
if (block==NULL) return NULL;
mi_assert_internal(page!=NULL);
if (ppage!=NULL) { *ppage = page; }
#if MI_PAGE_META_SMALL_IS_ALIGNED
// we should never allocate something allocated as small in a non-small page or otherwise aligned mi_free_small may fail.
// (that is why we need to limit the profiler_data_size as well)
// we should never allocate something allocated as small in a non-small page or otherwise aligned mi_free_small may fail.
if (size <= MI_SMALL_SIZE_MAX) { mi_assert_internal(mi_page_block_size(page) <= MI_SMALL_MAX_OBJ_SIZE); }
#endif
// Set up the profiled block as an interior pointer so the interior "slow" path is taken on mi_free (where we catch it to call on_free)
mi_page_set_has_interior_pointers(page, true);
block->next = MI_BLOCK_TAG_PROFILED;
p = (uint8_t*)block + user_offset;
mi_profiler_sample_data_t* const sample_data = (mi_profiler_sample_data_t*)((uint8_t*)block + sample_data_offset);
sample_data->user_data_size = sample_user_data_size;
// and call the profiler on_alloc
if (prof->on_alloc!=NULL) {
new_sample_rate = (*prof->on_alloc)(prof, sample_data, p, req_size, theap->profile_sample_rate, requested_since_last_sample, _mi_theap_heap(theap) );
}
}
if (new_sample_rate!=0 && new_sample_rate != (size_t)theap->profile_sample_rate) {
_mi_theap_set_profile_sample_rate(theap,new_sample_rate);
}
mi_theap_stat_counter_increase(theap,profile_samples,1);
return p;
}
void _mi_page_profile_on_free(mi_page_t* page, mi_block_t* block, void* p) {
mi_assert_internal(mi_block_ptr_is_sampled(block,p));
// get the heap and profiler
mi_heap_t* const heap = mi_page_heap(page);
if (heap==NULL) return;
mi_profiler_t* prof = mi_heap_profiler(heap);
if (prof==NULL || !mi_profiler_is_enabled(prof) || prof->on_free==NULL) return;
// call the on_free callback
mi_profiler_sample_data_t* const sample_data = (mi_profiler_sample_data_t*)((uint8_t*)block + sizeof(mi_block_t));
(*prof->on_free)(prof, sample_data, p, heap);
}
//----------------------------------------------------------------------------
// Profiling API
//-----------------------------------------------------------------------------*
size_t _mi_theap_set_profile_sample_rate(mi_theap_t* theap, size_t sample_rate) {
const size_t old_sample_rate = theap->profile_sample_rate;
theap->profile_sample_rate = (sample_rate > MI_SAMPLE_RATE_MAX ? MI_SAMPLE_RATE_MAX : sample_rate);
if (theap->profile_sample_countdown > theap->profile_sample_rate) {
theap->profile_sample_countdown = theap->profile_sample_rate; // todo: adjust difference?
}
_mi_theap_update_sample_rate(theap);
return old_sample_rate;
}
static bool mi_heap_set_profiler(mi_heap_t* heap, mi_profiler_t* profiler) {
mi_profiler_t* previous = (profiler==NULL ? mi_atomic_load_ptr_acquire(mi_profiler_t,&heap->profiler) : NULL); // don't overwrite unless it is NULL
return mi_atomic_cas_ptr_strong_acq_rel(mi_profiler_t, &heap->profiler, &previous, profiler);
}
mi_decl_export bool mi_heap_profile(mi_heap_t* heap, mi_profiler_t* profiler) {
mi_profiler_stop(profiler);
return mi_heap_set_profiler(heap,profiler);
}
mi_decl_export void mi_heap_profile_disable(mi_heap_t* heap) {
static mi_profiler_t mi_nosample_profiler = { NULL, 0, MI_SAMPLE_RATE_MAX, NULL, NULL, NULL };
mi_heap_set_profiler(heap,NULL); // first clear any previous one
mi_heap_set_profiler(heap,&mi_nosample_profiler); // and then install the nosample one
}
mi_decl_export bool mi_subproc_profile(mi_subproc_id_t subproc_id, mi_profiler_t* profiler) {
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
if (subproc==NULL) return false;
mi_profiler_stop(profiler);
mi_profiler_t* previous = (profiler==NULL ? mi_atomic_load_ptr_acquire(mi_profiler_t,&subproc->profiler) : NULL); // don't overwrite unless it is NULL
if (!mi_atomic_cas_ptr_strong_acq_rel(mi_profiler_t,&subproc->profiler, &previous, profiler)) { return false; }
mi_lock(&subproc->heaps_lock) {
for (mi_heap_t* heap = subproc->heaps; heap!=NULL; heap = heap->next) {
mi_heap_set_profiler(heap,profiler);
}
}
return true;
}
mi_decl_export bool mi_profile( mi_profiler_t* profiler) {
return mi_subproc_profile(mi_subproc_main(),profiler);
}
bool mi_profiler_start(mi_profiler_t* profiler ) {
if (profiler==NULL) return false;
const bool was_running = mi_profiler_set_enabled(profiler,true);
if (was_running) return true;
// for the main heap, if this is the profiler, start the theap more aggressively
// otherwise it will be picked up when theaps take the slow generic malloc path.
mi_heap_t* heap = mi_heap_main();
if (mi_heap_profiler(heap)==profiler) {
mi_theap_t* theap = _mi_heap_theap_peek(heap);
if (theap!=NULL) {
_mi_theap_set_profile_sample_rate(theap,1);
}
}
return false;
}
bool mi_profiler_stop(mi_profiler_t* profiler) {
if (profiler==NULL) return true;
return mi_profiler_set_enabled(profiler,false);
}
+11 -8
View File
@@ -24,20 +24,23 @@ terms of the MIT license. A copy of the license can be found in the file
#include "alloc-aligned.c"
#include "alloc-posix.c"
#include "arena.c"
#include "arena-meta.c"
#include "bitmap.c"
#include "heap.c"
#include "init.c"
#include "libc.c"
#include "options.c"
#include "os.c"
#include "page.c" // includes page-queue.c
#include "page-map.c"
#include "random.c"
#include "stats.c"
#include "theap.c"
#include "threadlocal.c"
#include "prim/prim.c"
#include "prim/prim.c" // includes platform specific prim/<platform>/prim.c
#include "prim/prim-tls.c"
#if MI_OSX_ZONE
#include "prim/osx/alloc-override-zone.c"
#endif
#include "page.c" // includes page-queue.c
#include "page-map.c"
#include "random.c"
#include "sample-guarded.c"
#include "sample-profile.c"
#include "stats.c"
#include "subproc.c"
#include "theap.c"
#include "threadlocal.c"
+120 -76
View File
@@ -1,5 +1,5 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -8,7 +8,8 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc-stats.h"
#include "mimalloc/internal.h"
#include "mimalloc/atomic.h"
#include "mimalloc/prim.h"
#include "mimalloc/prim.h" // _mi_prim_clock_now, mi_process_info_t
#include "mimalloc/prim-tls.h"
#include <string.h> // memset
@@ -38,26 +39,21 @@ static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {
if (amount > 0) { stat->total += amount; }
}
void __mi_stat_counter_increase_mt(mi_stat_counter_t* stat, size_t amount) {
mi_atomic_addi64_relaxed(&stat->total, (int64_t)amount);
}
void __mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {
stat->total += amount;
}
void __mi_stat_increase_mt(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_increase_mt(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_update_mt(stat, (int64_t)amount);
}
void __mi_stat_increase(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_increase(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_update(stat, (int64_t)amount);
}
void __mi_stat_decrease_mt(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_decrease_mt(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_update_mt(stat, -((int64_t)amount));
}
void __mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_decrease(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_update(stat, -((int64_t)amount));
}
@@ -68,26 +64,33 @@ void __mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
static void mi_stat_adjust_mt(mi_stat_count_t* stat, int64_t amount) {
if (amount == 0) return;
// adjust atomically
const int64_t peak = mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&stat->peak);
mi_atomic_addi64_relaxed(&stat->current, amount);
mi_atomic_addi64_relaxed(&stat->total, amount);
const int64_t prev_total = mi_atomic_addi64_relaxed(&stat->total, amount);
if (prev_total == peak) { mi_atomic_addi64_relaxed(&stat->peak, amount); }
}
static void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount) {
if (amount == 0) return;
stat->current += amount;
stat->total += amount;
if (stat->total==stat->peak) { stat->peak += amount; }
stat->total += amount;
}
void __mi_stat_adjust_increase_mt(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_adjust_increase_mt(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_adjust_mt(stat, (int64_t)amount);
}
void __mi_stat_adjust_increase(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_adjust_increase(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_adjust(stat, (int64_t)amount);
}
void __mi_stat_adjust_decrease_mt(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_adjust_decrease_mt(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_adjust_mt(stat, -((int64_t)amount));
}
void __mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount) {
void __mi_stat_adjust_decrease(mi_stat_count_t* stat, uint64_t amount) {
mi_assert_internal(amount<=INT64_MAX);
mi_stat_adjust(stat, -((int64_t)amount));
}
@@ -96,15 +99,17 @@ void __mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount) {
static void mi_stat_count_add_mt(mi_stat_count_t* stat, const mi_stat_count_t* src) {
if (stat==src) return;
mi_atomic_void_addi64_relaxed(&stat->total, &src->total);
const int64_t prev_current = mi_atomic_addi64_relaxed(&stat->current, src->current);
const int64_t src_peak = mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&src->peak);
const int64_t src_current = mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&src->current);
const int64_t prev_current = mi_atomic_addi64_relaxed(&stat->current, src_current);
// Global current plus thread peak approximates new global peak
// note: peak scores do really not work across threads.
// note: peak scores do not really work across threads.
// we used to just add them together but that often overestimates in practice.
// similarly, max does not seem to work well. The current approach
// by Artem Kharytoniuk (@artem-lunarg) seems to work better, see PR#1112
// for a longer description.
mi_atomic_maxi64_relaxed(&stat->peak, prev_current + src->peak);
// for a longer description.
mi_atomic_maxi64_relaxed(&stat->peak, prev_current + src_peak);
}
static void mi_stat_counter_add_mt(mi_stat_counter_t* stat, const mi_stat_counter_t* src) {
@@ -122,7 +127,7 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
// copy all fields
MI_STAT_FIELDS()
#if MI_STAT>1
#if MI_STATS
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
mi_stat_count_add_mt(&stats->malloc_bins[i], &src->malloc_bins[i]);
}
@@ -130,6 +135,9 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
mi_stat_count_add_mt(&stats->page_bins[i], &src->page_bins[i]);
}
for (size_t i = 0; i < MI_CBIN_COUNT; i++) {
mi_stat_count_add_mt(&stats->chunk_bins[i], &src->chunk_bins[i]);
}
}
#undef MI_STAT_COUNT
@@ -142,7 +150,7 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
// unit > 0 : size in binary bytes
// unit == 0: count as decimal
// unit < 0 : count in binary
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) {
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, bool limitwidth) {
char buf[32]; _mi_memzero_var(buf);
int len = 32;
const char* suffix = (unit <= 0 ? " " : "B");
@@ -167,12 +175,17 @@ static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void*
_mi_snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);
_mi_snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);
}
_mi_fprintf(out, arg, (fmt==NULL ? "%12s" : fmt), buf);
if (limitwidth) {
_mi_fprintf(out, arg, "%12s", buf);
}
else {
_mi_fprintf(out, arg, "%s", buf);
}
}
static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
mi_printf_amount(n,unit,out,arg,NULL);
mi_printf_amount(n,unit,out,arg,true);
}
static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
@@ -181,32 +194,31 @@ static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* ar
}
static void mi_stat_print_ex(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg, const char* notok ) {
_mi_fprintf(out, arg," %-10s:", msg);
_mi_fprintf(out, arg," %-12s:", msg);
if (unit != 0) {
if (unit > 0) {
if (unit > 0) { // as KiB (power of two)
mi_print_amount(stat->peak, unit, out, arg);
mi_print_amount(stat->total, unit, out, arg);
// mi_print_amount(stat->freed, unit, out, arg);
mi_print_amount(stat->current, unit, out, arg);
mi_print_amount(unit, 1, out, arg);
mi_print_count(stat->total, unit, out, arg);
}
else {
else if (unit==-1) { // as K (decimal)
mi_print_amount(stat->peak, -1, out, arg);
mi_print_amount(stat->total, -1, out, arg);
// mi_print_amount(stat->freed, -1, out, arg);
mi_print_amount(stat->current, -1, out, arg);
if (unit == -1) {
_mi_fprintf(out, arg, "%24s", "");
}
else {
mi_print_amount(-unit, 1, out, arg);
mi_print_count((stat->total / -unit), 0, out, arg);
}
_mi_fprintf(out, arg, "%24s", "");
}
else { // as KiB , unit is final count
mi_print_amount(stat->peak, 1, out, arg);
mi_print_amount(stat->total, 1, out, arg);
mi_print_amount(stat->current, -1, out, arg);
_mi_fprintf(out, arg, "%12s", "");
mi_print_count(-unit, 0, out, arg);
}
if (stat->current != 0) {
_mi_fprintf(out, arg, " ");
_mi_fprintf(out, arg, (notok == NULL ? "not all freed" : notok));
_mi_fprintf(out, arg, "%s", (notok == NULL ? "not all freed" : notok));
_mi_fprintf(out, arg, "\n");
}
else {
@@ -225,23 +237,23 @@ static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t
mi_stat_print_ex(stat, msg, unit, out, arg, NULL);
}
#if MI_STAT>1
static void mi_stat_total_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
_mi_fprintf(out, arg, " %-10s:", msg);
#if MI_STATS
static void mi_stat_total_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) {
_mi_fprintf(out, arg, " %-12s:", msg);
_mi_fprintf(out, arg, "%12s", " "); // no peak
mi_print_amount(stat->total, unit, out, arg);
mi_print_amount(stat->total, 1, out, arg);
_mi_fprintf(out, arg, "\n");
}
#endif
static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
_mi_fprintf(out, arg, " %-10s:", msg);
_mi_fprintf(out, arg, " %-12s:", msg);
mi_print_amount(stat->total, 0, out, arg);
_mi_fprintf(out, arg, "\n");
}
static void mi_stat_counter_print_size(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
_mi_fprintf(out, arg, " %-10s:", msg);
_mi_fprintf(out, arg, " %-12s:", msg);
mi_print_amount(stat->total, 1, out, arg);
_mi_fprintf(out, arg, "\n");
}
@@ -250,16 +262,16 @@ static void mi_stat_average_print(int64_t count, int64_t total, const char* msg,
const int64_t avg_tens = (count == 0 ? 0 : (total*10 / count));
const int64_t avg_whole = avg_tens/10;
const int64_t avg_frac1 = avg_tens%10;
_mi_fprintf(out, arg, " %-10s: %5lld.%lld avg\n", msg, avg_whole, avg_frac1);
_mi_fprintf(out, arg, " %-12s: %5lld.%lld avg\n", msg, avg_whole, avg_frac1);
}
static void mi_print_header(const char* name,mi_output_fun* out, void* arg ) {
_mi_fprintf(out, arg, " %-11s %11s %11s %11s %11s %11s\n",
_mi_fprintf(out, arg, " %-13s %11s %11s %11s %11s %11s\n",
name, "peak ", "total ", "current ", "block ", "total# ");
}
#if MI_STAT>1
#if MI_STATS
static bool mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, mi_output_fun* out, void* arg) {
bool found = false;
char buf[64];
@@ -328,13 +340,13 @@ mi_decl_export void mi_process_info_print_out(mi_output_fun* out, void* arg) mi_
size_t peak_commit;
size_t page_faults;
mi_process_info(&elapsed, &user_time, &sys_time, &current_rss, &peak_rss, &current_commit, &peak_commit, &page_faults);
_mi_fprintf(out, arg, " %-10s: %5zu.%03zu s\n", "elapsed", elapsed/1000, elapsed%1000);
_mi_fprintf(out, arg, " %-10s: user: %zu.%03zu s, system: %zu.%03zu s, faults: %zu, peak rss: ", "process",
_mi_fprintf(out, arg, " %-12s: %5zu.%03zu s\n", "elapsed", elapsed/1000, elapsed%1000);
_mi_fprintf(out, arg, " %-12s: user: %zu.%03zu s, system: %zu.%03zu s, faults: %zu, peak rss: ", "process",
user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, page_faults);
mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s");
mi_printf_amount((int64_t)peak_rss, 1, out, arg, false);
if (peak_commit > 0) {
_mi_fprintf(out, arg, ", peak commit: ");
mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s");
mi_printf_amount((int64_t)peak_commit, 1, out, arg, false);
}
_mi_fprintf(out, arg, "\n");
}
@@ -351,19 +363,21 @@ void _mi_stats_print(const char* name, size_t id, const mi_stats_t* stats, mi_ou
_mi_fprintf(out, arg, "%s %zu\n", name, id);
if (stats->malloc_normal.total + stats->malloc_huge.total != 0) {
#if MI_STAT>1
#if MI_STATS
mi_print_header("blocks", out, arg);
mi_stats_print_bins(stats->malloc_bins, MI_BIN_HUGE, out, arg);
#endif
#if MI_STAT
mi_stat_print(&stats->malloc_normal, "binned", (stats->malloc_normal_count.total == 0 ? -1 : 1), out, arg);
mi_stat_print(&stats->malloc_huge, "huge", (stats->malloc_huge_count.total == 0 ? -1 : 1), out, arg);
#if MI_STATS
mi_stat_print(&stats->malloc_normal, "binned", -stats->malloc_normal_count.total, out, arg);
mi_stat_print(&stats->malloc_huge, "huge", -stats->malloc_huge_count.total, out, arg);
mi_stat_count_t total = { 0,0,0 };
mi_stat_count_add_mt(&total, &stats->malloc_normal);
mi_stat_count_add_mt(&total, &stats->malloc_huge);
mi_stat_print_ex(&total, "total", 1, out, arg, "");
#if MI_STAT>1
mi_stat_total_print(&stats->malloc_requested, "malloc req", 1, out, arg);
mi_stat_print_ex(&total, "total", -(stats->malloc_normal_count.total + stats->malloc_huge_count.total), out, arg, "");
#if MI_STATS>=2
mi_stat_total_print(&stats->malloc_requested, "malloc req", out, arg);
#else
mi_stat_total_print(&stats->malloc_requested, "malloc req~", out, arg);
#endif
_mi_fprintf(out, arg, "\n");
#endif
@@ -377,13 +391,18 @@ void _mi_stats_print(const char* name, size_t id, const mi_stats_t* stats, mi_ou
// mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg);
mi_stat_print(&stats->pages, "pages", 0, out, arg);
mi_stat_print(&stats->pages_abandoned, "abandoned", 0, out, arg);
mi_stat_print(&stats->pages_os_abandoned, "os abandoned", 0, out, arg);
mi_stat_print(&stats->pages_os_allocated, "os allocated", 0, out, arg);
mi_stat_counter_print(&stats->pages_reclaim_on_alloc, "reclaima", out, arg);
mi_stat_counter_print(&stats->pages_reclaim_on_free, "reclaimf", out, arg);
mi_stat_counter_print(&stats->pages_reabandon_full, "reabandon", out, arg);
mi_stat_counter_print(&stats->pages_unabandon_busy_wait, "waits", out, arg);
mi_stat_counter_print(&stats->pages_extended, "extended", out, arg);
mi_stat_counter_print(&stats->pages_retire, "retire", out, arg);
mi_stat_counter_print(&stats->pages_stat_updates, "stat updates", out, arg);
mi_stat_average_print(stats->pages_stat_updates.total, stats->pages_stat_update_count.total, "stat avg", out, arg);
mi_stat_average_print(stats->page_searches_count.total, stats->page_searches.total, "searches", out, arg);
mi_stat_counter_print(&stats->profile_samples, "prof samples", out, arg);
_mi_fprintf(out, arg, "\n");
}
@@ -405,12 +424,13 @@ void _mi_stats_print(const char* name, size_t id, const mi_stats_t* stats, mi_ou
mi_stat_print_ex(&stats->heaps, "heaps", 0, out, arg, "");
mi_stat_counter_print(&stats->heaps_delete_wait, "heap waits", out, arg);
_mi_fprintf(out, arg, "\n");
mi_print_header("process", out, arg);
mi_stat_print_ex(&stats->threads, "threads", 0, out, arg, "");
_mi_fprintf(out, arg, " %-10s: %5i\n", "numa nodes", _mi_os_numa_node_count());
mi_process_info_print_out(out, arg);
}
mi_print_header("process", out, arg);
mi_stat_print_ex(&stats->threads, "threads", 0, out, arg, "");
_mi_fprintf(out, arg, " %-12s: %5i\n", "numa nodes", _mi_os_numa_node_count());
mi_process_info_print_out(out, arg);
_mi_fprintf(out, arg, "\n");
}
@@ -432,7 +452,7 @@ void _mi_stats_merge_into(mi_stats_t* to, mi_stats_t* from) {
mi_assert_internal(to != NULL && from != NULL);
if (to == from) return;
mi_stats_add(to, from);
_mi_memzero(from, sizeof(mi_stats_t));
mi_stats_init(from); // zero field and keep the header
}
static const mi_stats_t* mi_stats_merge_theap_to_heap(mi_theap_t* theap) mi_attr_noexcept {
@@ -449,11 +469,16 @@ static const mi_stats_t* mi_heap_get_stats(mi_heap_t* heap) {
else return mi_stats_merge_theap_to_heap(theap);
}
static const mi_stats_t* mi_theap_get_stats(mi_theap_t* theap) {
return &theap->stats;
}
// deprecated
void mi_stats_reset(void) mi_attr_noexcept {
if (!mi_theap_is_initialized(_mi_theap_default())) return;
mi_heap_get_stats(mi_heap_main());
mi_heap_stats_merge_to_subproc(mi_heap_main());
mi_heap_t* heap_main = mi_heap_main();
mi_heap_get_stats(heap_main);
mi_heap_stats_merge_to_subproc(heap_main);
}
@@ -480,6 +505,10 @@ void mi_subproc_heap_stats_print_out(mi_subproc_id_t subproc_id, mi_output_fun*
if (subproc==NULL) return;
mi_heap_print_visit_info_t vinfo = { out, arg };
mi_subproc_visit_heaps(subproc_id, &mi_heap_print_visitor, &vinfo);
if (subproc->theap_meta!=NULL) {
_mi_stats_print("meta", subproc->subproc_seq, &subproc->theap_meta->stats, out, arg);
}
// if (subproc->theap_meta!=NULL) { _mi_stats_merge_into(&subproc->stats, &subproc->theap_meta->stats); }
_mi_stats_print("subproc", subproc->subproc_seq, &subproc->stats, out, arg);
}
@@ -548,8 +577,10 @@ mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, s
pinfo.elapsed = _mi_clock_end(mi_process_start);
{ const mi_subproc_t* subproc = _mi_subproc_main();
if (subproc!=NULL) {
pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.current)));
pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.peak)));
const int64_t current = mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.current));
const int64_t peak = mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)(&subproc->stats.committed.peak));
pinfo.current_commit = (current < 0 ? 0 : (current < PTRDIFF_MAX ? (size_t)current : PTRDIFF_MAX));
pinfo.peak_commit = (peak < 0 ? 0 : (peak < PTRDIFF_MAX ? (size_t)peak : PTRDIFF_MAX));
}
}
pinfo.current_rss = pinfo.current_commit;
@@ -601,6 +632,14 @@ bool mi_heap_stats_get(mi_heap_t* heap, mi_stats_t* stats) mi_attr_noexcept {
return mi_stats_copy(stats, mi_heap_get_stats(heap));
}
bool mi_theap_stats_get(mi_theap_t* theap, mi_stats_t* stats) mi_attr_noexcept {
return mi_stats_copy(stats, mi_theap_get_stats(theap));
}
void mi_theap_stats_merge_to_heap(mi_theap_t* theap) mi_attr_noexcept {
if (theap == NULL) return;
_mi_theap_merge_stats(theap);
}
static bool mi_cdecl mi_heap_aggregate_visitor(mi_heap_t* heap, void* arg) {
mi_stats_t* stats = (mi_stats_t*)arg;
@@ -614,6 +653,8 @@ bool mi_subproc_stats_get(mi_subproc_id_t subproc_id, mi_stats_t* stats) mi_attr
if (subproc == NULL) return false;
if (!mi_stats_copy(stats, &subproc->stats)) return false;
mi_subproc_visit_heaps(subproc_id, &mi_heap_aggregate_visitor, stats);
// hide meta data stats
// if (subproc->theap_meta!=NULL) { mi_stats_add_into(stats, &subproc->theap_meta->stats); }
return true;
}
@@ -722,9 +763,6 @@ static void mi_json_buf_print_counter_value(mi_json_buf_t* hbuf, const char* nam
mi_json_buf_print_value(hbuf, name, stat->total);
}
#define MI_STAT_COUNT(stat) mi_json_buf_print_count_value(&hbuf, #stat, &stats->stat);
#define MI_STAT_COUNTER(stat) mi_json_buf_print_counter_value(&hbuf, #stat, &stats->stat);
static char* mi_stats_get_json_from(const mi_stats_t* stats, size_t output_size, char* output_buf) mi_attr_noexcept {
if (stats==NULL || stats->size!=sizeof(mi_stats_t) || stats->version!=MI_STAT_VERSION) return NULL;
mi_json_buf_t hbuf = { NULL, 0, 0, true };
@@ -763,7 +801,13 @@ static char* mi_stats_get_json_from(const mi_stats_t* stats, size_t output_size,
mi_json_buf_print(&hbuf, " },\n");
// statistics
#define MI_STAT_COUNT(stat) mi_json_buf_print_count_value(&hbuf, #stat, &stats->stat);
#define MI_STAT_COUNTER(stat) mi_json_buf_print_counter_value(&hbuf, #stat, &stats->stat);
MI_STAT_FIELDS()
#undef MI_STAT_COUNT
#undef MI_STAT_COUNTER
// size bins
mi_json_buf_print(&hbuf, " \"malloc_bins\": [\n");
@@ -782,7 +826,7 @@ static char* mi_stats_get_json_from(const mi_stats_t* stats, size_t output_size,
}
mi_json_buf_print(&hbuf, " ]\n");
mi_json_buf_print(&hbuf, "}\n");
if (hbuf.used >= hbuf.size) {
if (hbuf.used + 1 >= hbuf.size) {
// failed
if (hbuf.can_realloc) { mi_free(hbuf.buf); }
return NULL;
+327
View File
@@ -0,0 +1,327 @@
/* ----------------------------------------------------------------------------
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim-tls.h"
// pre-allocate the main subprocess structure.
static mi_decl_cache_align mi_subproc_t mi_process_subproc_main = mi_init_struct_zero;
static mi_subproc_t* mi_subprocs = NULL;
static mi_lock_t mi_subprocs_lock = MI_LOCK_INITIALIZER;
/* -----------------------------------------------------------
Meta-data allocation
We allocate thread local data and theaps through a dedicated
theap `subproc.theap_meta` which uses a detached tld with
a detached thread id. The initial theap_meta is statically
allocated and can thus be used to allocate on an as yet
uninitialized thread or process.
We need to take a lock though to allocate safely on the
detached `theap_meta`.
----------------------------------------------------------- */
void* _mi_meta_zalloc( mi_subproc_t* subproc, size_t size, mi_memid_t* memid ) {
mi_assert_internal(subproc->theap_meta != NULL);
void* p = NULL;
mi_lock(&subproc->theap_meta_lock) {
p = mi_theap_zalloc(subproc->theap_meta, size);
if (memid != NULL) { *memid = (p==NULL ? _mi_memid_none() : _mi_memid_create_malloc(p,size,true) ); }
}
return p;
}
void* _mi_meta_zalloc_aligned( mi_subproc_t* subproc, size_t size, size_t aligned, mi_memid_t* memid ) {
mi_assert_internal(subproc->theap_meta != NULL);
void* p = NULL;
mi_lock(&subproc->theap_meta_lock) {
p = mi_theap_zalloc_aligned(subproc->theap_meta, size, aligned);
if (memid != NULL) { *memid = (p==NULL ? _mi_memid_none() : _mi_memid_create_malloc(p,size,true) ); }
}
return p;
}
void* _mi_meta_rezalloc( mi_subproc_t* subproc, void* oldp, size_t newsize, mi_memid_t* memid ) {
mi_assert_internal(subproc->theap_meta != NULL);
// note: since we take a meta lock we cannot use `mi_theap_rezalloc` as that could call `mi_free` which
// can call `mi_stat_free` which would try to take the meta lock again. See issue #1358.
void* p = NULL;
mi_lock(&subproc->theap_meta_lock) {
p = mi_theap_zalloc(subproc->theap_meta, newsize);
}
if (p!=NULL) {
if (oldp!=NULL) {
const size_t oldsize = mi_usable_size(oldp);
const size_t copysize = (newsize < oldsize ? newsize : oldsize);
_mi_memcpy(p,oldp,copysize);
if (memid!=NULL) { _mi_meta_free(subproc,oldp,*memid); }
else { mi_free(oldp); }
}
if (memid!=NULL) { *memid = _mi_memid_create_malloc(p,newsize,true); }
}
else {
if (memid!=NULL) { *memid = _mi_memid_none(); }
}
return p;
}
void _mi_meta_free(mi_subproc_t* subproc, void* p, mi_memid_t memid) {
if (p==NULL || mi_memid_needs_no_free(memid)) return;
if (memid.memkind == MI_MEM_MALLOC) {
mi_free(p);
}
else {
mi_assert_internal(subproc!=NULL);
_mi_arenas_free(subproc, p, _mi_memid_size(memid), memid);
}
}
bool _mi_meta_is_meta_page(const mi_subproc_t* subproc, const mi_page_t* page) {
if (page==NULL) return false;
mi_theap_t* theap = page->theap;
return (theap != NULL && theap == subproc->theap_meta);
}
/* -----------------------------------------------------------
Sub process helpers
----------------------------------------------------------- */
mi_subproc_t* _mi_subproc_main(void) {
return &mi_process_subproc_main;
}
bool _mi_subproc_is_main(mi_subproc_t* subproc) {
return (subproc == &mi_process_subproc_main);
}
mi_subproc_t* _mi_subproc(void) {
mi_theap_t* theap = _mi_theap_default();
if (theap == NULL || theap->tld == NULL || theap->tld->subproc == NULL) { // see issue #1289 and #1391
return _mi_subproc_main();
}
else {
return theap->tld->subproc;
}
}
mi_heap_t* mi_heap_main(void) {
return _mi_subproc_heap_main(_mi_subproc()); // don't use mi_theap_main_init_get() so this call works during process_init
}
mi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) {
return (mi_subproc_t*)(subproc_id._mi_subproc_id);
}
mi_subproc_id_t _mi_subproc_to_id(mi_subproc_t* subproc) {
mi_subproc_id_t id = { subproc };
return id;
}
mi_subproc_id_t mi_subproc_main(void) {
return _mi_subproc_to_id(_mi_subproc_main());
}
mi_subproc_id_t mi_subproc_current(void) {
return _mi_subproc_to_id(_mi_subproc());
}
/* -----------------------------------------------------------
Sub process creation
----------------------------------------------------------- */
static mi_subproc_t* mi_subproc_init(mi_subproc_t* subproc, mi_subproc_t* parent) {
static _Atomic(size_t) subproc_total_count;
subproc->parent = parent;
subproc->subproc_seq = mi_atomic_increment_relaxed(&subproc_total_count);
mi_stats_header_init(&subproc->stats);
mi_lock_init(&subproc->arena_reserve_lock);
mi_lock_init(&subproc->heaps_lock);
mi_lock_init(&subproc->theap_meta_lock);
mi_lock(&mi_subprocs_lock) {
// push on subproc list
subproc->next = mi_subprocs;
if (mi_subprocs!=NULL) { mi_subprocs->prev = subproc; }
mi_subprocs = subproc;
}
return subproc;
}
mi_subproc_id_t mi_subproc_new(void) {
mi_thread_init();
mi_subproc_t* const parent = _mi_subproc();
mi_memid_t memid;
mi_subproc_t* const subproc = (mi_subproc_t*)_mi_meta_zalloc(parent, sizeof(mi_subproc_t), &memid);
if (subproc == NULL) { return _mi_subproc_to_id(NULL); }
subproc->memid = memid;
mi_memid_t theap_memid;
mi_theap_t* const theap_meta = (mi_theap_t*)_mi_meta_zalloc(parent, sizeof(mi_theap_t), &theap_memid);
if (theap_meta==NULL) {
_mi_meta_free(parent, subproc, memid);
return _mi_subproc_to_id(NULL);
}
theap_meta->memid = memid;
// init subproc
mi_subproc_init(subproc,parent);
// init main heap
mi_heap_t* heap_main = _mi_heap_new_for_subproc(subproc,0,true);
if (heap_main==NULL) {
_mi_meta_free(parent, theap_meta, theap_meta->memid);
mi_subproc_destroy(_mi_subproc_to_id(subproc));
return _mi_subproc_to_id(NULL);
}
mi_assert_internal(subproc->heap_main == heap_main);
// init meta theap
mi_assert_internal(parent->theap_meta!=NULL);
mi_assert_internal(parent->theap_meta->tld!=NULL);
mi_assert_internal(parent->theap_meta->tld->thread_id == MI_THREADID_DETACHED);
_mi_theap_init(theap_meta,heap_main,parent->theap_meta->tld /* detached tld */);
subproc->theap_meta = theap_meta;
return _mi_subproc_to_id(subproc);
}
/* -----------------------------------------------------------
Sub process destruction
----------------------------------------------------------- */
// destroy all subproc resources including arena's, heap's etc.
static void mi_subproc_unsafe_destroy(mi_subproc_t* subproc, bool acquire_subprocs_lock)
{
if (subproc==NULL) return;
// remove from the subproc list
mi_lock_maybe(&mi_subprocs_lock, acquire_subprocs_lock) {
if (subproc->next!=NULL) { subproc->next->prev = subproc->prev; }
if (subproc->prev!=NULL) { subproc->prev->next = subproc->next; }
else { mi_assert_internal(mi_subprocs==subproc); mi_subprocs = subproc->next; }
}
// destroy all subproc heaps
mi_lock(&subproc->heaps_lock) {
mi_heap_t* heap = subproc->heaps;
while (heap != NULL) {
mi_heap_t* next = heap->next;
if (heap!=subproc->heap_main) { _mi_heap_force_destroy(heap, false /* don't re-acquire the heaps_lock */); }
heap = next;
}
mi_assert_internal(subproc->heap_main==NULL || subproc->heaps == subproc->heap_main);
if (subproc->heap_main!=NULL) {
_mi_thread_locals_thread_done(); // release thread locals that may have been allocated (safe as the main heap uses the fast key)
if (_mi_subproc_is_main(subproc)) {
_mi_thread_locals_done();
}
_mi_heap_force_destroy(subproc->heap_main, false /* don't re-acquire the heaps_lock */); // no warning if destroying the main heap
}
}
subproc->theap_meta = NULL; // theap meta stats are merged during heap_destroy of the main heap
if (!_mi_subproc_is_main(subproc)) {
// merge stats back into the main subproc
_mi_stats_merge_into(&mi_process_subproc_main.stats, &subproc->stats);
}
// remove associated arenas
_mi_arenas_unsafe_destroy_all(subproc);
// show stats of the main process (at process end) before releasing the heaps lock
if (_mi_subproc_is_main(subproc)) {
if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) {
mi_subproc_stats_print_out(mi_subproc_main(), NULL, NULL);
}
}
// todo: should we refcount subprocesses?
mi_lock_done(&subproc->arena_reserve_lock);
mi_lock_done(&subproc->heaps_lock);
mi_lock_done(&subproc->theap_meta_lock);
_mi_meta_free( subproc->parent, subproc, subproc->memid);
if (_mi_subproc_is_main(subproc)) {
// for the main subproc, also release the global page map
_mi_page_map_unsafe_destroy();
}
}
void mi_subproc_destroy(mi_subproc_id_t subproc_id) {
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
if (subproc==NULL || subproc==&mi_process_subproc_main) return;
mi_subproc_unsafe_destroy(subproc, true /* take lock */);
}
void _mi_subprocs_unsafe_destroy_all(void) {
mi_lock(&mi_subprocs_lock) {
mi_subproc_t* subproc = mi_subprocs;
while (subproc!=NULL) {
mi_subproc_t* next = subproc->next;
if (subproc!=&mi_process_subproc_main) {
mi_subproc_unsafe_destroy(subproc, false /* take mi_subprocs lock */);
}
subproc = next;
}
}
mi_subproc_unsafe_destroy(&mi_process_subproc_main, true /* take mi_subprocs lock */);
}
/* -----------------------------------------------------------
Sub process various
----------------------------------------------------------- */
void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) {
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
mi_assert_internal(subproc!=NULL);
if (subproc==NULL) return;
mi_assert_internal(subproc->heap_main!=NULL);
if (subproc->heap_main==NULL) return;
mi_theap_t* theap = _mi_theap_default();
if (mi_theap_is_initialized(theap)) {
if (theap->tld!=NULL && theap->tld->subproc != subproc) {
_mi_warning_message("unable to add thread to the subprocess as it was already in another subprocess (at %p)\n", theap->tld->subproc);
}
return;
}
// initialize this thread tld & theap
_mi_thread_init_with_heap(subproc->heap_main);
}
bool mi_subproc_visit_heaps(mi_subproc_id_t subproc_id, mi_heap_visit_fun* visitor, void* arg) {
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
if (subproc==NULL) return false;
bool ok = true;
mi_lock(&subproc->heaps_lock) {
for (mi_heap_t* heap = subproc->heaps; heap!=NULL && ok; heap = heap->next) {
ok = (*visitor)(heap, arg);
}
}
return ok;
}
mi_subproc_t* _mi_subproc_main_init(void) {
mi_lock_init(&mi_subprocs_lock);
mi_memid_t memid = _mi_memid_create_static(&mi_process_subproc_main,sizeof(mi_subproc_t));
mi_process_subproc_main.memid = memid;
mi_subproc_init(&mi_process_subproc_main,NULL);
return &mi_process_subproc_main;
}
void _mi_subproc_main_done(void) {
mi_lock_done(&mi_subprocs_lock);
}
+192 -217
View File
@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
Copyright (c) 2018-2026, Microsoft Research, Daan Leijen
This is free software; you can redistribute it and/or modify it under the
terms of the MIT license. A copy of the license can be found in the file
"LICENSE" at the root of this distribution.
@@ -7,7 +7,8 @@ terms of the MIT license. A copy of the license can be found in the file
#include "mimalloc.h"
#include "mimalloc/internal.h"
#include "mimalloc/prim.h" // _mi_theap_default
#include "mimalloc/prim.h" // _mi_prim_thread_yield
#include "mimalloc/prim-tls.h" // _mi_theap_default
#if defined(_MSC_VER) && (_MSC_VER < 1920)
#pragma warning(disable:4204) // non-constant aggregate initializer
@@ -23,7 +24,7 @@ typedef bool (theap_page_visitor_fun)(mi_theap_t* theap, mi_page_queue_t* pq, mi
// Visit all pages in a theap; returns `false` if break was called.
static bool mi_theap_visit_pages(mi_theap_t* theap, theap_page_visitor_fun* fn, bool include_full, void* arg1, void* arg2)
{
if (theap==NULL || theap->page_count==0) return 0;
if (theap==NULL || theap->page_count==0) return true;
// visit all pages
#if MI_DEBUG>1
@@ -50,19 +51,24 @@ static bool mi_theap_visit_pages(mi_theap_t* theap, theap_page_visitor_fun* fn,
}
#if MI_DEBUG>=2
#if MI_DEBUG>=3
static bool mi_theap_page_is_valid(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {
MI_UNUSED(arg1);
MI_UNUSED(arg2);
MI_UNUSED(pq);
mi_assert_internal(mi_page_theap(page) == theap);
mi_theap_t* const page_theap = _mi_heap_theap_peek(page->heap);
mi_assert_internal(page_theap == NULL || theap == page_theap);
mi_assert_expensive(_mi_page_is_valid(page));
return true;
}
#endif
#if MI_DEBUG>=3
static bool mi_theap_is_valid(mi_theap_t* theap) {
mi_assert_internal(theap!=NULL);
mi_heap_t* const heap = _mi_theap_heap_peek(theap);
mi_assert_internal(heap != NULL);
mi_theap_t* const heap_theap = _mi_heap_theap_peek(heap); // don't use mi_heap_theap as that may re-initialize the thread
mi_assert_internal(heap_theap==NULL || heap_theap == theap);
mi_theap_visit_pages(theap, &mi_theap_page_is_valid, true, NULL, NULL);
for (size_t bin = 0; bin < MI_BIN_COUNT; bin++) {
mi_assert_internal(_mi_page_queue_is_valid(theap, &theap->pages[bin]));
@@ -91,9 +97,10 @@ typedef enum mi_collect_e {
static bool mi_theap_page_collect(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg_collect, void* arg2 ) {
MI_UNUSED(arg2);
MI_UNUSED(theap);
mi_assert_internal(mi_theap_page_is_valid(theap, pq, page, NULL, NULL));
mi_assert_expensive(mi_theap_page_is_valid(theap, pq, page, NULL, NULL));
mi_collect_t collect = *((mi_collect_t*)arg_collect);
_mi_page_free_collect(page, collect >= MI_FORCE);
_mi_page_free_collect(page, collect >= MI_FORCE); // update used count
_mi_page_update_stats(page);
if (mi_page_all_free(page)) {
// no more used blocks, possibly free the page.
if (collect >= MI_FORCE || page->retire_expire == 0) { // either forced/abandon, or not already retired
@@ -108,9 +115,10 @@ static bool mi_theap_page_collect(mi_theap_t* theap, mi_page_queue_t* pq, mi_pag
return true; // don't break
}
static void mi_theap_merge_stats(mi_theap_t* theap) {
mi_assert_internal(mi_theap_is_initialized(theap));
_mi_stats_merge_into(&_mi_theap_heap(theap)->stats, &theap->stats);
void _mi_theap_merge_stats(mi_theap_t* theap) {
mi_assert_internal(mi_theap_is_initialized(theap));
mi_heap_t* const heap = _mi_theap_heap(theap);
_mi_stats_merge_into(&heap->stats, &theap->stats);
}
static void mi_theap_collect_ex(mi_theap_t* theap, mi_collect_t collect)
@@ -124,18 +132,22 @@ static void mi_theap_collect_ex(mi_theap_t* theap, mi_collect_t collect)
// python/cpython#112532: we may be called from a thread that is not the owner of the theap
// const bool is_main_thread = (_mi_is_main_thread() && theap->thread_id == _mi_thread_id());
// collect retired pages
_mi_theap_collect_retired(theap, force);
// collect retired pages (and full pages if theap->allow_page_abandon is false)
_mi_theap_collect_retired(theap, force);
// collect all pages owned by this thread
mi_theap_visit_pages(theap, &mi_theap_page_collect, (collect!=MI_NORMAL), &collect, NULL); // dont normally visit full pages, see issue #1220
// collect arenas (this is program wide so don't force purges on abandonment of threads)
//mi_atomic_storei64_release(&theap->tld->subproc->purge_expire, 1);
_mi_arenas_collect(collect == MI_FORCE /* force purge? */, collect >= MI_FORCE /* visit all? */, theap->tld);
if (collect != MI_ABANDON) {
_mi_arenas_collect(collect == MI_FORCE /* force purge? */, collect >= MI_FORCE /* visit all? */, theap->tld);
}
// merge statistics
mi_theap_merge_stats(theap);
if (mi_option_is_enabled(mi_option_collect_merges_stats)) {
_mi_theap_merge_stats(theap);
}
}
void _mi_theap_collect_abandon(mi_theap_t* theap) {
@@ -170,24 +182,41 @@ mi_theap_t* mi_theap_get_default(void) {
return theap;
}
mi_theap_t* mi_theap_set_default(mi_theap_t* theap) {
mi_theap_t* const previous = mi_theap_get_default();
if (mi_theap_is_initialized(theap)) {
_mi_theap_default_set(theap);
}
return previous;
}
static void mi_theap_options_init(mi_theap_t* theap) {
theap->allow_page_reclaim = (mi_option_get(mi_option_page_reclaim_on_free) >= 0);
theap->allow_page_abandon = (mi_option_get(mi_option_page_full_retain) >= 0);
theap->page_full_retain = mi_option_get_clamp(mi_option_page_full_retain, -1, 32);
theap->is_detached = (theap->tld->thread_id == MI_THREADID_DETACHED);
}
// todo: make order of parameters consistent (but would that break compat with CPython?)
void _mi_theap_init(mi_theap_t* theap, mi_heap_t* heap, mi_tld_t* tld)
{
mi_assert_internal(theap!=NULL);
mi_assert_internal(heap!=NULL);
mi_assert_internal(tld!=NULL);
mi_memid_t memid = theap->memid;
_mi_memcpy_aligned(theap, &_mi_theap_empty, sizeof(mi_theap_t));
theap->memid = memid;
theap->refcount = 1;
theap->tld = tld; // avoid reading the thread-local tld during initialization
mi_atomic_store_ptr_relaxed(mi_heap_t,&theap->heap,heap);
mi_atomic_store_release(&theap->refcount,1);
mi_atomic_store_ptr_release(mi_subproc_t,&theap->subproc,heap->subproc);
mi_assert_internal(theap->stats.size == sizeof(mi_stats_t));
_mi_theap_options_init(theap);
mi_theap_options_init(theap);
if (theap->tld->is_in_threadpool) {
// if we run as part of a thread pool it is better to not arbitrarily reclaim abandoned pages into our theap.
// this is checked in `free.c:mi_free_try_collect_mt`
// .. but abandoning is good in this case: halve the full page retain (possibly to 0)
// .. but abandoning is good in this case: quarter the full page retain (possibly to 0)
// (so blocked threads do not hold on to too much memory)
if (theap->page_full_retain > 0) {
theap->page_full_retain = theap->page_full_retain / 4;
@@ -196,29 +225,42 @@ void _mi_theap_init(mi_theap_t* theap, mi_heap_t* heap, mi_tld_t* tld)
// push on the thread local theaps list
mi_theap_t* head = NULL;
mi_random_ctx_t head_random;
mi_lock(&theap->tld->theaps_lock) {
head = theap->tld->theaps;
theap->tprev = NULL;
theap->tnext = head;
if (head!=NULL) { head->tprev = theap; }
theap->tld->theaps = theap;
if (head!=NULL) {
head->tprev = theap;
head_random = head->random;
}
}
// initialize random
if (head == NULL) { // first theap in this thread?
// initialize random if heap==NULL
if (head==NULL) { // first theap of the first thread?
#if defined(_WIN32) && !defined(MI_SHARED_LIB)
if (tld->thread_seq==0) {
_mi_random_init_weak(&theap->random); // prevent allocation failure during bcrypt dll initialization with static linking (issue #1185)
#else
_mi_random_init(&theap->random);
}
else
#endif
{
_mi_random_init(&theap->random);
}
}
else {
_mi_random_split(&head->random, &theap->random);
_mi_random_split(&head_random, &theap->random); // &theap->random is used as nonce so it is ok if threads capture the same head->random
}
// theap->cookie = _mi_theap_random_next(theap) | 1;
_mi_theap_guarded_init(theap); // needs theap->random
if (!theap->is_detached) {
mi_subproc_stat_increase(_mi_theap_subproc(theap),theaps,1); // on subproc to match theap_free_mem
}
theap->cookie = _mi_theap_random_next(theap) | 1;
_mi_theap_guarded_init(theap);
mi_subproc_stat_increase(_mi_subproc(),theaps,1);
// only now set the heap member as it is used to determine if a theap is initialized
mi_atomic_store_ptr_release(mi_heap_t,&theap->heap,heap);
// push on the heap's theap list
mi_lock(&heap->theaps_lock) {
head = heap->theaps;
@@ -229,33 +271,37 @@ void _mi_theap_init(mi_theap_t* theap, mi_heap_t* heap, mi_tld_t* tld)
}
}
mi_theap_t* _mi_theap_create(mi_heap_t* heap, mi_tld_t* tld) {
mi_theap_t* _mi_theap_alloc(mi_heap_t* heap, mi_tld_t* tld) {
mi_assert_internal(tld!=NULL);
mi_assert_internal(heap!=NULL);
mi_assert_internal(tld->thread_id == MI_THREADID_DETACHED || _mi_thread_id() == tld->thread_id);
// mi_assert_internal(_mi_heap_theap_peek(heap)==NULL); // don't access thread locals as this is called on thread init
// allocate and initialize a theap
mi_memid_t memid;
mi_theap_t* theap;
//if (!_mi_is_heap_main(heap)) {
// theap = (mi_theap_t*)mi_heap_zalloc(mi_heap_main(),sizeof(mi_theap_t));
// memid = _mi_memid_create(MI_MEM_HEAP_MAIN);
// memid.initially_zero = memid.initially_committed = true;
//}
//else
if (heap->exclusive_arena == NULL) {
theap = (mi_theap_t*)_mi_meta_zalloc(sizeof(mi_theap_t), &memid);
theap = (mi_theap_t*)_mi_meta_zalloc(heap->subproc, sizeof(mi_theap_t), &memid);
}
else {
// theaps associated with a specific arena are allocated in that arena
// note: takes up at least one slice which is quite wasteful...
const size_t size = _mi_align_up(sizeof(mi_theap_t),MI_ARENA_MIN_OBJ_SIZE);
theap = (mi_theap_t*)_mi_arenas_alloc(heap, size, true, true, heap->exclusive_arena, tld->thread_seq, tld->numa_node, &memid);
mi_assert_internal(memid.mem.os.size >= size);
theap = (mi_theap_t*)_mi_arenas_alloc(heap, size, true, true, heap->exclusive_arena, tld->thread_seq, tld->numa_node, &memid);
}
if (theap==NULL) {
_mi_error_message(ENOMEM, "unable to allocate theap meta-data\n");
return NULL;
}
theap->memid = memid;
return theap;
}
mi_theap_t* _mi_theap_create(mi_heap_t* heap, mi_tld_t* tld) {
mi_theap_t* theap = _mi_theap_alloc(heap,tld);
if (theap == NULL) return NULL;
_mi_theap_init(theap, heap, tld);
return theap;
}
@@ -266,205 +312,129 @@ uintptr_t _mi_theap_random_next(mi_theap_t* theap) {
static void mi_theap_free_mem(mi_theap_t* theap) {
if (theap!=NULL) {
mi_subproc_stat_decrease(_mi_subproc(),theaps,1);
// free the used memory
if (theap->memid.memkind == MI_MEM_HEAP_MAIN) { // note: for now unused as it would access theap_default stats in mi_free of the current theap
mi_assert_internal(_mi_is_heap_main(mi_heap_of(theap)));
mi_free(theap);
}
else if (theap->memid.memkind == MI_MEM_META) {
_mi_meta_free(theap, sizeof(*theap), theap->memid);
}
else {
_mi_arenas_free(theap, _mi_align_up(sizeof(*theap),MI_ARENA_MIN_OBJ_SIZE), theap->memid ); // issue #1168, avoid assertion failure
mi_subproc_t* const subproc = mi_atomic_load_ptr_relaxed(mi_subproc_t,&theap->subproc);
if (!theap->is_detached) {
mi_subproc_stat_decrease(subproc,theaps,1);
}
_mi_meta_free(subproc, theap, theap->memid);
}
}
// we need to reference count theaps due to the _mi_theap_cached thread locals
void _mi_theap_incref(mi_theap_t* theap) {
if (theap!=NULL && theap->memid.memkind > MI_MEM_STATIC) {
if (theap!=NULL && !mi_memid_needs_no_free(theap->memid)) {
mi_atomic_increment_acq_rel(&theap->refcount);
}
}
void _mi_theap_decref(mi_theap_t* theap) {
if (theap!=NULL && theap->memid.memkind > MI_MEM_STATIC) {
if (theap!=NULL && !mi_memid_needs_no_free(theap->memid)) {
if (mi_atomic_decrement_acq_rel(&theap->refcount) == 1) {
mi_theap_free_mem(theap);
}
}
}
// Thread termination and heap delete/destroy might run concurrently
// and we need to ensure we free the memory correctly. A heap or tld
// will first "detach" its theaps so it has a list with theaps that are
// no longer shared, and only then free's the theaps in that list.
// To detach we need to hold both the `heap->theaps_lock` and the `tld->theaps_lock`.
// Due to lock-inversion we need to use `mi_lock_try_acquire` and if that fails
// we back-off, release the outer lock, and try again until we succeed.
// called from `mi_theap_delete` to free the internal theap resources.
bool _mi_theap_free(mi_theap_t* theap, bool acquire_heap_theaps_lock, bool acquire_tld_theaps_lock) {
mi_assert(theap != NULL);
if (theap==NULL) return true;
mi_heap_t* const heap = mi_atomic_exchange_ptr_acq_rel(mi_heap_t, &theap->heap, NULL);
if (heap==NULL) {
// concurrent interaction, retry in an outer loop (as the other thread may be blocked on our lock)
return false;
}
else {
// merge stats to the owning heap
_mi_stats_merge_into(&heap->stats, &theap->stats);
// remove ourselves from the heap theaps list
mi_lock_maybe(&heap->theaps_lock, acquire_heap_theaps_lock) {
if (theap->hnext != NULL) { theap->hnext->hprev = theap->hprev; }
if (theap->hprev != NULL) { theap->hprev->hnext = theap->hnext; }
else { mi_assert_internal(heap->theaps == theap); heap->theaps = theap->hnext; }
theap->hnext = theap->hprev = NULL;
// Remove the theaps in this heap from any thread local tld lists.
void _mi_heap_detach_theaps( mi_heap_t* heap ) {
bool all_detached;
do {
all_detached = true;
mi_lock(&heap->theaps_lock) {
mi_theap_t* theap = heap->theaps;
while (theap != NULL) {
mi_theap_t* next = theap->hnext;
mi_tld_t* tld = theap->tld;
if (tld != NULL) {
if (mi_lock_try_acquire(&tld->theaps_lock)) {
// remove the theap from the tld theaps list
if (theap->tnext != NULL) { theap->tnext->tprev = theap->tprev; }
if (theap->tprev != NULL) { theap->tprev->tnext = theap->tnext; }
else { mi_assert_internal(theap->tld->theaps == theap); theap->tld->theaps = theap->tnext; }
theap->tnext = theap->tprev = NULL;
theap->tld = NULL;
mi_lock_release(&tld->theaps_lock);
}
else {
all_detached = false;
}
}
theap = next;
}
}
// remove ourselves from the thread local theaps list
mi_lock_maybe(&theap->tld->theaps_lock, acquire_tld_theaps_lock) {
if (theap->tnext != NULL) { theap->tnext->tprev = theap->tprev; }
if (theap->tprev != NULL) { theap->tprev->tnext = theap->tnext; }
else { mi_assert_internal(theap->tld->theaps == theap); theap->tld->theaps = theap->tnext; }
theap->tnext = theap->tprev = NULL;
if (!all_detached) {
mi_subproc_stat_counter_increase(heap->subproc,heaps_delete_wait,1);
_mi_prim_thread_yield();
}
theap->tld = NULL;
_mi_theap_decref(theap);
return true;
}
} while (!all_detached);
}
// Remove the theaps in this thread from the heaps that own them.
void _mi_tld_detach_theaps( mi_tld_t* tld ) {
bool all_detached;
do {
all_detached = true;
mi_lock(&tld->theaps_lock) {
mi_theap_t* theap = tld->theaps;
while (theap != NULL) {
mi_theap_t* next = theap->tnext;
mi_assert_internal(theap->page_count==0);
mi_heap_t* heap = _mi_theap_heap_peek(theap); // now the heap might be NULL from an earlier iteration
if (heap != NULL) {
if (mi_lock_try_acquire(&heap->theaps_lock)) {
// merge stats into the owning heap stats
_mi_stats_merge_into(&heap->stats, &theap->stats);
// remove the theap from the heap list
if (theap->hnext != NULL) { theap->hnext->hprev = theap->hprev; }
if (theap->hprev != NULL) { theap->hprev->hnext = theap->hnext; }
else { mi_assert_internal(heap->theaps == theap); heap->theaps = theap->hnext; }
theap->hnext = theap->hprev = NULL;
// and set `heap` to NULL
mi_atomic_store_ptr_release(mi_heap_t, &theap->heap, NULL);
mi_lock_release(&heap->theaps_lock);
}
else {
all_detached = false;
}
}
theap = next;
}
}
if (!all_detached) {
mi_subproc_stat_counter_increase(tld->subproc,heaps_delete_wait,1);
_mi_prim_thread_yield();
}
} while (!all_detached);
}
/* -----------------------------------------------------------
Heap destroy
----------------------------------------------------------- */
/*
// zero out the page queues
static void mi_theap_reset_pages(mi_theap_t* theap) {
mi_assert_internal(theap != NULL);
mi_assert_internal(mi_theap_is_initialized(theap));
// TODO: copy full empty theap instead?
_mi_memset(&theap->pages_free_direct, 0, sizeof(theap->pages_free_direct));
_mi_memcpy_aligned(&theap->pages, &_mi_theap_empty.pages, sizeof(theap->pages));
// theap->thread_delayed_free = NULL;
theap->page_count = 0;
}
static bool _mi_theap_page_destroy(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {
MI_UNUSED(arg1);
MI_UNUSED(arg2);
MI_UNUSED(pq);
// ensure no more thread_delayed_free will be added
//_mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);
// stats
const size_t bsize = mi_page_block_size(page);
if (bsize > MI_LARGE_MAX_OBJ_SIZE) {
mi_theap_stat_decrease(theap, malloc_huge, bsize);
}
#if (MI_STAT>0)
_mi_page_free_collect(page, false); // update used count
const size_t inuse = page->used;
if (bsize <= MI_LARGE_MAX_OBJ_SIZE) {
mi_theap_stat_decrease(theap, malloc_normal, bsize * inuse);
#if (MI_STAT>1)
mi_theap_stat_decrease(theap, malloc_bins[_mi_bin(bsize)], inuse);
#endif
}
// mi_theap_stat_decrease(theap, malloc_requested, bsize * inuse); // todo: off for aligned blocks...
#endif
/// pretend it is all free now
mi_assert_internal(mi_page_thread_free(page) == NULL);
page->used = 0;
// and free the page
// mi_page_free(page,false);
page->next = NULL;
page->prev = NULL;
mi_page_set_theap(page, NULL);
_mi_arenas_page_free(page, theap);
return true; // keep going
}
void _mi_theap_destroy_pages(mi_theap_t* theap) {
mi_theap_visit_pages(theap, &_mi_theap_page_destroy, NULL, NULL);
mi_theap_reset_pages(theap);
}
#if MI_TRACK_HEAP_DESTROY
static bool mi_cdecl mi_theap_track_block_free(const mi_theap_t* theap, const mi_theap_area_t* area, void* block, size_t block_size, void* arg) {
MI_UNUSED(theap); MI_UNUSED(area); MI_UNUSED(arg); MI_UNUSED(block_size);
mi_track_free_size(block,mi_usable_size(block));
return true;
}
#endif
void mi_theap_destroy(mi_theap_t* theap) {
mi_assert(theap != NULL);
mi_assert(mi_theap_is_initialized(theap));
mi_assert(!theap->allow_page_reclaim);
mi_assert(!theap->allow_page_abandon);
mi_assert_expensive(mi_theap_is_valid(theap));
if (theap==NULL || !mi_theap_is_initialized(theap)) return;
#if MI_GUARDED
// _mi_warning_message("'mi_theap_destroy' called but MI_GUARDED is enabled -- using `mi_theap_delete` instead (theap at %p)\n", theap);
mi_theap_delete(theap);
return;
#else
if (theap->allow_page_reclaim) {
_mi_warning_message("'mi_theap_destroy' called but ignored as the theap was not created with 'allow_destroy' (theap at %p)\n", theap);
// don't free in case it may contain reclaimed pages,
mi_theap_delete(theap);
}
else {
// track all blocks as freed
#if MI_TRACK_HEAP_DESTROY
mi_theap_visit_blocks(theap, true, mi_theap_track_block_free, NULL);
#endif
// free all pages
_mi_theap_destroy_pages(theap);
mi_theap_free(theap,true);
}
#endif
}
// forcefully destroy all theaps in the current thread
void _mi_theap_unsafe_destroy_all(mi_theap_t* theap) {
mi_assert_internal(theap != NULL);
if (theap == NULL) return;
mi_theap_t* curr = theap->tld->theaps;
while (curr != NULL) {
mi_theap_t* next = curr->next;
if (!curr->allow_page_reclaim) {
mi_theap_destroy(curr);
}
else {
_mi_theap_destroy_pages(curr);
}
curr = next;
}
}
*/
/* -----------------------------------------------------------
Safe Heap delete
Safe theap delete
----------------------------------------------------------- */
// Safe delete a theap without freeing any still allocated blocks in that theap.
void _mi_theap_delete(mi_theap_t* theap, bool acquire_tld_theaps_lock)
{
mi_assert(theap != NULL);
mi_assert(mi_theap_is_initialized(theap));
mi_assert_expensive(mi_theap_is_valid(theap));
if (theap==NULL || !mi_theap_is_initialized(theap)) return;
// void _mi_theap_delete(mi_theap_t* theap, bool acquire_tld_theaps_lock)
// {
// mi_assert(theap != NULL);
// mi_assert(mi_theap_is_initialized(theap));
// mi_assert_expensive(mi_theap_is_valid(theap));
// if (theap==NULL || !mi_theap_is_initialized(theap)) return;
// abandon all pages
_mi_theap_collect_abandon(theap);
// // abandon all pages
// _mi_theap_collect_abandon(theap);
mi_assert_internal(theap->page_count==0);
_mi_theap_free(theap, true /* acquire heap->theaps_lock */, acquire_tld_theaps_lock);
}
// mi_assert_internal(theap->page_count==0);
// _mi_theap_free(theap, true /* acquire heap->theaps_lock */, acquire_tld_theaps_lock);
// }
@@ -518,7 +488,7 @@ bool mi_theap_reload(mi_theap_t* theap, mi_arena_id_t arena_id) {
// reinit direct pages (as we may be in a different process)
mi_assert_internal(theap->page_count == 0);
for (size_t i = 0; i < MI_PAGES_DIRECT; i++) {
theap->pages_free_direct[i] = (mi_page_t*)&_mi_page_empty;
theap->pages_free_direct[i] = _mi_page_empty_get();
}
// push on the thread local theaps list
@@ -541,7 +511,7 @@ void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) {
area->reserved = page->reserved * bsize;
area->committed = page->capacity * bsize;
area->blocks = mi_page_start(page);
area->used = page->used; // number of blocks in use (#553)
area->used = mi_page_used(page); // number of blocks in use (#553)
area->block_size = ubsize;
area->full_block_size = bsize;
area->reserved1 = page;
@@ -567,7 +537,7 @@ bool _mi_theap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi
_mi_page_free_collect(page,true); // collect both thread_delayed and local_free
mi_assert_internal(page->local_free == NULL);
if (page->used == 0) return true;
if (mi_page_used(page) == 0) return true;
size_t psize;
uint8_t* const pstart = mi_page_area(page, &psize);
@@ -577,13 +547,13 @@ bool _mi_theap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi
// optimize page with one block
if (page->capacity == 1) {
mi_assert_internal(page->used == 1 && page->free == NULL);
mi_assert_internal(mi_page_used(page) == 1 && page->free == NULL);
return visitor(heap, area, pstart, ubsize, arg);
}
mi_assert(bsize <= UINT32_MAX);
// optimize full pages
if (page->used == page->capacity) {
if (mi_page_used(page) == page->capacity) {
uint8_t* block = pstart;
for (size_t i = 0; i < page->capacity; i++) {
if (!visitor(heap, area, block, ubsize, arg)) return false;
@@ -627,7 +597,7 @@ bool _mi_theap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi
size_t bit = blockidx - (bitidx * MI_INTPTR_BITS);
free_map[bitidx] |= ((uintptr_t)1 << bit);
}
mi_assert_internal(page->capacity == (free_count + page->used));
mi_assert_internal(page->capacity == (free_count + mi_page_used(page)));
// walk through all blocks skipping the free ones
#if MI_DEBUG>1
@@ -659,10 +629,15 @@ bool _mi_theap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi
block += bsize * MI_INTPTR_BITS;
}
}
mi_assert_internal(page->used == used_count);
mi_assert_internal(mi_page_used(page) == used_count);
return true;
}
// bool _mi_page_visit_blocks( mi_page_t* page, mi_block_visit_fun* visitor, void* arg ) {
// mi_heap_area_t area;
// _mi_heap_area_init(&area, page);
// return _mi_theap_area_visit_blocks(&area, page, visitor, arg);
// }
// Separate struct to keep `mi_page_t` out of the public interface
+111 -31
View File
@@ -6,9 +6,9 @@ terms of the MIT license. A copy of the license can be found in the file
-----------------------------------------------------------------------------*/
/* ----------------------------------------------------------------------------
Implement dynamic thread local variables (for heap's).
Unlike most OS native implementations there is no limit on the number
that can be allocated.
Implement dynamic thread local variables (used by heap's for their theap's).
Unlike most OS native implementations there is no limit on the number
that can be allocated.
-----------------------------------------------------------------------------*/
#include "mimalloc.h"
@@ -27,12 +27,41 @@ typedef struct mi_tls_slot_s {
typedef struct mi_thread_locals_s {
size_t count;
mi_memid_t memid;
mi_tls_slot_t slots[1];
} mi_thread_locals_t;
static mi_thread_locals_t mi_thread_locals_empty = { 0, {{0,NULL}} };
static mi_thread_locals_t mi_thread_locals_empty = mi_init_struct_zero;
mi_decl_thread mi_thread_locals_t* mi_thread_locals = &mi_thread_locals_empty; // always point to a valid `mi_thread_locals_t`
/* -----------------------------------------------------------
We have 2 thread local variable which we implement with either
a C thread local declaration or using pthread keys.
- mi_thread_locals: points to an array of thread locals for most keys
- mi_slot_fast: a single dedicated thread local for slightly faster access. (used for the main heap's theap)
----------------------------------------------------------- */
#if MI_TLS_MODEL_PTHREADS || defined(__APPLE__) // macOS has fast pthreads
// Use pthreads
#define mi_define_thread_local(tp,name,initval) \
static pthread_key_t __##name##_key = MI_PTHREAD_KEY_INVALID; \
static inline tp name##_peek(void) { return (tp)mi_pthread_key_get(__##name##_key); } \
static inline tp name##_get(void) { tp result = name##_peek(); return (result!=NULL ? result : initval); } \
static inline bool name##_set(tp val) { return mi_pthread_key_set(&__##name##_key,val); } \
static inline void name##_delete(void) { mi_pthread_key_delete(&__##name##_key); }
#else
// Direct thread locals
#define mi_define_thread_local(tp,name,initval) \
static mi_decl_thread tp __##name = initval; \
static inline tp name##_peek(void) { return __##name; } \
static inline tp name##_get(void) { tp result = __##name; return (result!=NULL ? result : initval); } \
static inline bool name##_set(tp val) { __##name = val; return true; } \
static inline void name##_delete(void) { }
#endif
mi_define_thread_local(mi_thread_locals_t*, mi_thread_locals, &mi_thread_locals_empty)
mi_define_thread_local(void*, mi_slot_fast, NULL)
/* -----------------------------------------------------------
@@ -42,15 +71,18 @@ mi_decl_thread mi_thread_locals_t* mi_thread_locals = &mi_thread_locals_empty;
a value, we also set the version of the key.
----------------------------------------------------------- */
#if MI_SIZE_BITS < 64
#define MI_TLS_IDX_BITS (MI_SIZE_BITS/2) // half for the index, half for the version
#if MI_SIZE_BITS >= 64
#define MI_TLS_IDX_BITS (MI_SIZE_BITS/4) /* 16 bits for the index, 48 bits for the version */
#elif MI_SIZE_BITS >= 32
#define MI_TLS_IDX_BITS (12) /* 12 bits for index, 20 for the version? */
#else
#define MI_TLS_IDX_BITS (MI_SIZE_BITS/4) // 16 bits for the index, 48 bits for the version
#error not enough bits for the version for thread locals
#endif
#define MI_TLS_IDX_MASK ((MI_ZU(1)<<MI_TLS_IDX_BITS)-1)
#define MI_TLS_IDX_MAX MI_TLS_IDX_MASK
#define MI_TLS_VERSION_MAX ((MI_ZU(1)<<(MI_SIZE_BITS - MI_TLS_IDX_BITS))-1)
static size_t mi_key_index( size_t key ) {
return (key & MI_TLS_IDX_MASK);
}
@@ -70,36 +102,43 @@ static mi_thread_local_t mi_key_create( size_t index, size_t version ) {
// dynamically reallocate the thread local slots when needed
static mi_thread_locals_t* mi_thread_locals_expand(size_t least_idx) {
mi_thread_locals_t* tls_old = mi_thread_locals;
mi_thread_locals_t* tls_old = mi_thread_locals_get();
const size_t count_old = tls_old->count;
size_t count;
if (count_old==0) {
tls_old = NULL; // so we allocate fresh from mi_thread_locals_empty
count = 16; // start with 16 slots
}
}
else if (count_old >= 1024) {
count = count_old + 1024; // at some point increase linearly
}
else {
count = 2*count_old; // and double initially
}
if (count <= least_idx) {
if (count <= least_idx) {
count = least_idx + 1;
}
if (count > MI_TLS_IDX_MAX) { return NULL; } // too large
mi_thread_locals_t* tls = (mi_thread_locals_t*)mi_rezalloc(tls_old, sizeof(mi_thread_locals_t) + count*sizeof(mi_tls_slot_t));
// allocate as meta (for secure mode)
// we could also allocate on the main heap; this is recursion safe as that uses the fast local key
mi_memid_t memid = (tls_old==NULL ? _mi_memid_none() : tls_old->memid);
mi_thread_locals_t* tls = (mi_thread_locals_t*)_mi_meta_rezalloc(_mi_subproc(), tls_old, sizeof(mi_thread_locals_t) + count*sizeof(mi_tls_slot_t), &memid);
if mi_unlikely(tls==NULL) return NULL;
tls->memid = memid;
tls->count = count;
mi_thread_locals = tls;
mi_thread_locals_set(tls);
return tls;
}
static mi_decl_noinline bool mi_thread_local_set_expand( mi_thread_local_t key, void* val ) {
if (val==NULL) return true;
const size_t idx = mi_key_index(key);
const size_t idx = mi_key_index(key);
mi_thread_locals_t* tls = mi_thread_locals_expand(idx);
if (tls==NULL) return false;
mi_assert_internal(tls == mi_thread_locals);
if (tls==NULL) {
_mi_error_message(EFAULT,"unable to allocate thread local variables\n");
return false;
}
mi_assert_internal(tls == mi_thread_locals_get());
mi_assert_internal(idx < tls->count);
tls->slots[idx].value = val;
tls->slots[idx].version = mi_key_version(key);
@@ -108,8 +147,8 @@ static mi_decl_noinline bool mi_thread_local_set_expand( mi_thread_local_t key,
// set a tls slot; returns `true` if successful.
// Can return `false` if we could not reallocate the slots array.
bool _mi_thread_local_set( mi_thread_local_t key, void* val ) {
mi_thread_locals_t* tls = mi_thread_locals;
static mi_decl_noinline bool mi_thread_local_set_regular( mi_thread_local_t key, void* val ) {
mi_thread_locals_t* tls = mi_thread_locals_get();
mi_assert_internal(tls!=NULL);
mi_assert_internal(key!=0);
const size_t idx = mi_key_index(key);
@@ -123,25 +162,54 @@ bool _mi_thread_local_set( mi_thread_local_t key, void* val ) {
}
}
// get a tls slot value
void* _mi_thread_local_get( mi_thread_local_t key ) {
const mi_thread_locals_t* const tls = mi_thread_locals;
mi_assert_internal(tls!=NULL);
bool _mi_thread_local_set( mi_thread_local_t key, void* val ) {
mi_assert_internal(key!=0);
if (key == mi_thread_local_key_fast) {
return mi_slot_fast_set(val);
}
else {
return mi_thread_local_set_regular(key,val);
}
}
// get a tls slot value
static mi_decl_noinline void* mi_thread_local_get_regular( mi_thread_local_t key ) {
const mi_thread_locals_t* const tls = mi_thread_locals_peek();
if mi_unlikely(tls==NULL) {
// this can happen if a thread local is accessed after the thread local has been freed
// from mi_thread_done or mi_process_done.
// todo: can we remove this check? now we can still call this from process done when stats are printed (which calls mi_heap_theap_peek)
return NULL;
}
const size_t idx = mi_key_index(key);
if mi_likely(idx < tls->count && mi_key_version(key) == tls->slots[idx].version) {
return tls->slots[idx].value;
}
else {
return NULL;
return NULL;
}
}
// get a thread local value
void* _mi_thread_local_get( mi_thread_local_t key ) {
if mi_likely(key == mi_thread_local_key_fast) {
return mi_slot_fast_get();
}
else {
void* p = mi_thread_local_get_regular(key);
if (key==0) { mi_assert_internal(p==NULL); }
return p;
}
}
void _mi_thread_locals_thread_done(void) {
mi_thread_locals_t* const tls = mi_thread_locals;
mi_thread_locals_t* const tls = mi_thread_locals_peek();
if (tls!=NULL && tls->count > 0) {
mi_free(tls);
mi_thread_locals = &mi_thread_locals_empty;
_mi_meta_free(_mi_subproc(), tls, tls->memid);
mi_thread_locals_set(NULL);
}
if (mi_slot_fast_peek() != NULL) {
mi_slot_fast_set(NULL);
}
}
@@ -152,6 +220,7 @@ Create and free fresh TLS key's
static mi_lock_t mi_thread_locals_lock; // we need a lock in order to re-allocate the slot bits
static mi_bitmap_t* mi_thread_locals_free; // reuse an arena bitmap to track which slots were assigned (1=free, 0=in-use)
static mi_memid_t mi_thread_locals_memid; // provenance of mi_thread_locals_free
static size_t mi_thread_locals_version; // version to be able to reuse slots safely
void _mi_thread_locals_init(void) {
@@ -161,9 +230,13 @@ void _mi_thread_locals_init(void) {
void _mi_thread_locals_done(void) {
mi_lock(&mi_thread_locals_lock) {
mi_bitmap_t* const slots = mi_thread_locals_free;
mi_free(slots);
if (slots!=NULL) {
_mi_meta_free(_mi_subproc_main(), slots, mi_thread_locals_memid);
}
}
mi_lock_done(&mi_thread_locals_lock);
mi_thread_locals_delete();
mi_slot_fast_delete();
}
// strange signature but allows us to reuse the arena code for claiming free pages
@@ -173,7 +246,7 @@ static bool mi_thread_local_claim_fun(size_t _slice_index, mi_arena_t* _arena, b
return true;
}
// When we claim a free slot, we increase the global version counter
// When we claim a free slot, we increase the global version counter
// (so if we reuse a slot it will be returning NULL initially when a thread tries to get it)
static mi_thread_local_t mi_thread_local_claim(void) {
size_t idx = 0;
@@ -194,16 +267,21 @@ static bool mi_thread_local_create_expand(void) {
const size_t newcount = 1024 + oldcount;
if (newcount > MI_TLS_IDX_MAX) { return false; }
const size_t newsize = mi_bitmap_size( newcount, NULL );
mi_bitmap_t* newslots = (mi_bitmap_t*)mi_zalloc_aligned(newsize, MI_BCHUNK_SIZE);
// mi_bitmap_t* newslots = (mi_bitmap_t*)mi_zalloc_aligned(newsize, MI_BCHUNK_SIZE);
mi_memid_t memid;
mi_bitmap_t* newslots = (mi_bitmap_t*)_mi_meta_zalloc_aligned(_mi_subproc_main(), newsize, MI_BCHUNK_SIZE, &memid); // always allocate thread locals in the main subprocess
mi_assert_internal(_mi_is_aligned(newslots,MI_BCHUNK_SIZE));
if (newslots==NULL) { return false; }
if (slots!=NULL) {
// copy over the previous bitmap
_mi_memcpy_aligned(newslots, slots, mi_bitmap_size(oldcount, NULL));
mi_free(slots);
const size_t oldsize = mi_bitmap_size(oldcount,NULL);
_mi_memcpy_aligned(newslots, slots, oldsize);
_mi_meta_free(_mi_subproc_main(), slots, mi_thread_locals_memid);
}
mi_bitmap_init(newslots, newcount, true /* pretend already zero'd so we do not zero out the copied old entries */);
mi_bitmap_unsafe_setN(newslots, oldcount, newcount - oldcount); /* set the new expanded slots as available */
mi_thread_locals_free = newslots;
mi_thread_locals_memid = memid;
return true;
}
@@ -219,6 +297,8 @@ mi_thread_local_t _mi_thread_local_create(void) {
}
}
}
mi_assert_internal(key!=0);
mi_assert_internal(key!=mi_thread_local_key_fast);
return key;
}