Snapshot support for embedders

JA
3mo ago
+273-51

Allocator side of Bun's heap images (an embedder freezes a process's heap into a file and maps it into fresh processes). 240 lines; everything is behind new entry points or embedder-supplied defines, and the malloc/free fast paths are unchanged. Full rationale in the second commit's message.

PieceWhatCost when unused
MI_HEAP_IMAGE_HOST_FN / MI_HEAP_IMAGE_BUILD_ENV, mi_os_hint_floor()Deterministic address hints in image-capable executables; a process that may map an image keeps its early heap above the image areanone unless the define is set (Bun sets it; the predicate is one call per OS allocation)
MI_THREADID_FROZEN, mi_arenas_freeze_pages()Builder marks every page before writing it out; frees of image blocks then land on the existing cross-thread slow path and are dropped there; collects/sweeps skip them; abandoned maps no longer list themone compare on the cross-thread free path
mi_arenas_seal_existing(), mi_theap_freeze(), mi_arenas_visit_free_ranges()Restore-side: no allocation into image arenas, imaged main theap left alone, builder can omit free spacenone
mi_malloc_zone_process_owned_ranges() (macOS)The zone structs libsystem registered for this process must survive the image overlaynone
Hole-sweep state moved from __thread variables onto the swept tldStandalone fix: under the macOS zone override, a new thread's first malloc that collected a purged page read a TLV, dyld allocated the TLV block with malloc, and the allocator re-entered itself without bound. The guard now reads the page's own tldnone (the guard was already only reached for purged pages)
prof.c: mi_prof_reinit_lock, mi_prof_visit_live, mi_prof_lock_is_freeProfiler lock is nobody's after fork/restore; live-sample visitor for attribution toolingnone

Exercised by test/js/bun/heap-image on the Bun side (oven-sh/bun branch claude/lowmem-cc) and by an application that has been running from an image for a day. Companion: oven-sh/WebKit#397 (JSC side). Draft until the Bun side is up for review.