Summary
On macOS, every memory-profiling tool (vmmap, footprint, Instruments' VM Tracker) attributes the bulk of a compiled Perry binary's memory to IOAccelerator — i.e. GPU driver memory. On an allocation-heavy benchmark this looks like a ~640 MB GPU leak in a headless console program:
REGION TYPE SIZE RESIDENT DIRTY
IOAccelerator 644.4M 422.1M 422.1M 8
IOAccelerator (reserved) 384.0M 0K 0K 1
There is no GPU memory involved. This is the Perry JS heap: perry-runtime routes all allocation through mimalloc (#[global_allocator], issue #62, crates/perry-runtime/src/lib.rs), and mimalloc tags its mmap regions with VM tag 100 (mi_option_os_tag default). macOS tooling decodes tag 100 as IOAccelerator.
Proof
mi_option_os_tag is env-tunable on any existing binary. Re-running the same binary with the tag moved:
$ MIMALLOC_OS_TAG=240 ./bench & vmmap -summary $!
Memory Tag 240 644.4M 422.1M 422.1M 8
Memory Tag 240 (reserved) 384.0M 0K 0K 1
Identical regions, identical sizes — only the label changes. IOAccelerator disappears entirely.
Repro
// churn.ts — allocation-churn benchmark
class Node2 { v: number; next: Node2 | null;
constructor(v: number, next: Node2 | null) { this.v = v; this.next = next; } }
let sum = 0;
for (let round = 0; round < 200; round++) {
let head: Node2 | null = null;
for (let i = 0; i < 20000; i++) head = new Node2(i, head);
let p = head; while (p !== null) { sum += p.v; p = p.next; }
}
console.log(sum);
$ perry compile churn.ts -o churn && ./churn & vmmap -summary $!
Environment: macOS 26.5 (25F71), Apple Silicon, Perry v0.5.1264.
Why it matters
- Anyone profiling Perry memory on macOS starts by chasing a GPU ghost (this cost a full investigation detour before
MIMALLOC_OS_TAG proved the collision).
- It also hides the JS heap from attribution: the arena, GC metadata, and Rust-side structures all pool into one mislabeled category, so you can't tell heap from allocator overhead in Instruments.
Suggested fixes
- Set
mi_option_os_tag to a distinctive unused tag at runtime init (e.g. 240) so tooling shows a neutral Memory Tag 240 instead of IOAccelerator. One line via mi_option_set at startup (or ship MIMALLOC_OS_TAG in docs as a stopgap).
- Document the collision in the profiling/memory docs (
docs/memory.md / CONTRIBUTING): "Perry heap shows as IOAccelerator in vmmap/Instruments; it is mimalloc VM tag 100."
- Longer term: allocate arena blocks via direct
mmap with Perry's own VM_MAKE_TAG, separating "JS heap" from "Rust allocator" in every tool. (This is also the fix direction for a related macOS RSS-retention issue — mimalloc purges via MADV_FREE, which stays in RSS/phys_footprint, so PERRY_GC_HEAP_LIMIT cannot bound what the allocator layer retains; details in a follow-up issue with measurements.)
Summary
On macOS, every memory-profiling tool (
vmmap,footprint, Instruments' VM Tracker) attributes the bulk of a compiled Perry binary's memory toIOAccelerator— i.e. GPU driver memory. On an allocation-heavy benchmark this looks like a ~640 MB GPU leak in a headless console program:There is no GPU memory involved. This is the Perry JS heap:
perry-runtimeroutes all allocation through mimalloc (#[global_allocator], issue #62,crates/perry-runtime/src/lib.rs), and mimalloc tags itsmmapregions with VM tag 100 (mi_option_os_tagdefault). macOS tooling decodes tag 100 asIOAccelerator.Proof
mi_option_os_tagis env-tunable on any existing binary. Re-running the same binary with the tag moved:Identical regions, identical sizes — only the label changes.
IOAcceleratordisappears entirely.Repro
Environment: macOS 26.5 (25F71), Apple Silicon, Perry v0.5.1264.
Why it matters
MIMALLOC_OS_TAGproved the collision).Suggested fixes
mi_option_os_tagto a distinctive unused tag at runtime init (e.g. 240) so tooling shows a neutralMemory Tag 240instead ofIOAccelerator. One line viami_option_setat startup (or shipMIMALLOC_OS_TAGin docs as a stopgap).docs/memory.md/ CONTRIBUTING): "Perry heap shows as IOAccelerator in vmmap/Instruments; it is mimalloc VM tag 100."mmapwith Perry's ownVM_MAKE_TAG, separating "JS heap" from "Rust allocator" in every tool. (This is also the fix direction for a related macOS RSS-retention issue — mimalloc purges viaMADV_FREE, which stays in RSS/phys_footprint, soPERRY_GC_HEAP_LIMITcannot bound what the allocator layer retains; details in a follow-up issue with measurements.)