fix(alloc): stop macOS reporting the mimalloc heap as IOAccelerator GPU memory

The 0.10.1 plan read the TUI's idle "61 MB IOAccelerator" as AppKit/
CoreAnimation initialising at startup. It is not: mimalloc tags its macOS
VM regions with tag 100, which macOS names VM_MEMORY_IOACCELERATOR, so
footprint, vmmap and Activity Monitor show the app's whole heap as GPU
memory. Evidence on 0.10.0 release and current debug builds:
- MIMALLOC_OS_TAG=254 relabels the same ~66 MB as "app-specific tag 15".
- A dlopen interposer sees no Metal/AGX load; the only WindowServer touch
  is the one-shot CGMainDisplayID refresh probe, which allocates ~0 (a
  standalone probe of the same calls stays at 2.2 MB).
- Linking AppKit/Vision/CoreGraphics alone costs <1 MB.

Retag the heap to 254 (VM_MEMORY_APPLICATION_SPECIFIC_15) from a Mach-O
initializer in both binaries (codewhale, codewhale-tui). This has to run
from an initializer because setting the option from main is too late: the
tag sticks to the arena reserved on the first allocation (checked in a
scratch crate). An explicit MIMALLOC_OS_TAG still wins. The byte count is
unchanged; this fixes the labelling only.

MCP: boot is already lazy (#6033). Only required servers and servers
covered by an explicit tool selection start at session start.

Checks: cargo build -p codewhale-tui --bin codewhale-tui ok; cargo build
-p codewhale-cli --bin codewhale ok; rustfmt --check on both files ok.
Idle footprint in tmux 200x50: debug codewhale-tui 63 MB "app-specific
tag 15" (control on the same binary with MIMALLOC_OS_TAG=100: 59 MB
"IOAccelerator"); debug codewhale 71 MB "app-specific tag 15", 0
IOAccelerator. No unit test: the effect is a process-level VM tag,
verified with footprint. No tracking issue exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-09-22 16:48:09 -07:00
1 parent 2178e69ee0
commit 791b775f75
2 files changed
+64

No files matched your search

+32
View File
@@ -5,6 +5,38 @@
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
// mimalloc tags its macOS VM regions with tag 100 by default, which macOS
// names `VM_MEMORY_IOACCELERATOR`. `footprint`, `vmmap` and Activity Monitor
// then report the whole heap as GPU memory ("61 MB IOAccelerator" at idle),
// which reads as AppKit/CoreAnimation initialising when nothing GPU-related
// runs. Retag to 254 (VM_MEMORY_APPLICATION_SPECIFIC_15) so the heap is
// labelled as the app's own memory. This must run before the first
// allocation, because the tag sticks to the arena mimalloc reserves then;
// setting it from `main` is already too late, so it runs as a Mach-O
// initializer. An explicit `MIMALLOC_OS_TAG` still wins.
#[cfg(all(
target_os = "macos",
feature = "mimalloc-allocator",
not(feature = "rusty-alloc")
))]
#[used]
#[unsafe(link_section = "__DATA,__mod_init_func")]
static MIMALLOC_RETAG: extern "C" fn() = {
extern "C" fn retag_mimalloc_heap() {
unsafe extern "C" {
fn mi_option_set(option: std::ffi::c_int, value: std::ffi::c_long);
}
/// `mi_option_os_tag` in mimalloc's `mi_option_e`.
const MI_OPTION_OS_TAG: std::ffi::c_int = 18;
if std::env::var_os("MIMALLOC_OS_TAG").is_none() {
// SAFETY: mimalloc's option setter is callable before its own
// initialisation; it only stores the value.
unsafe { mi_option_set(MI_OPTION_OS_TAG, 254) };
}
}
retag_mimalloc_heap
};
#[cfg(feature = "rusty-alloc")]
#[global_allocator]
static GLOBAL: rusty_alloc_api::RustyAlloc = rusty_alloc_api::RustyAlloc;
+32
View File
@@ -5,6 +5,38 @@
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
// mimalloc tags its macOS VM regions with tag 100 by default, which macOS
// names `VM_MEMORY_IOACCELERATOR`. `footprint`, `vmmap` and Activity Monitor
// then report the whole heap as GPU memory ("61 MB IOAccelerator" at idle),
// which reads as AppKit/CoreAnimation initialising when nothing GPU-related
// runs. Retag to 254 (VM_MEMORY_APPLICATION_SPECIFIC_15) so the heap is
// labelled as the app's own memory. This must run before the first
// allocation, because the tag sticks to the arena mimalloc reserves then;
// setting it from `main` is already too late, so it runs as a Mach-O
// initializer. An explicit `MIMALLOC_OS_TAG` still wins.
#[cfg(all(
target_os = "macos",
feature = "mimalloc-allocator",
not(feature = "rusty-alloc")
))]
#[used]
#[unsafe(link_section = "__DATA,__mod_init_func")]
static MIMALLOC_RETAG: extern "C" fn() = {
extern "C" fn retag_mimalloc_heap() {
unsafe extern "C" {
fn mi_option_set(option: std::ffi::c_int, value: std::ffi::c_long);
}
/// `mi_option_os_tag` in mimalloc's `mi_option_e`.
const MI_OPTION_OS_TAG: std::ffi::c_int = 18;
if std::env::var_os("MIMALLOC_OS_TAG").is_none() {
// SAFETY: mimalloc's option setter is callable before its own
// initialisation; it only stores the value.
unsafe { mi_option_set(MI_OPTION_OS_TAG, 254) };
}
}
retag_mimalloc_heap
};
#[cfg(feature = "rusty-alloc")]
#[global_allocator]
static GLOBAL: rusty_alloc_api::RustyAlloc = rusty_alloc_api::RustyAlloc;