mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
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:
commit
fec0b87b4c
76 files changed
+8572
-4472
No files matched your search
+25
-7
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(®, 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]) {
|
||||
|
||||
@@ -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
@@ -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, ®);
|
||||
refine_ast_signatures(&ctx, root, ®);
|
||||
@@ -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, ®);
|
||||
refine_ast_signatures(&ctx, root, ®);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+407
-329
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
+518
-255
File diff suppressed because it is too large.
Load diff
+200
-147
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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));
|
||||
}
|
||||
+341
-872
File diff suppressed because it is too large.
Load diff
+100
-31
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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;
|
||||
}
|
||||
+534
-202
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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, ¤t_rss, &peak_rss, ¤t_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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user