mimalloc_pprof/sys.rs
1//! Raw bindings to the in-tree mimalloc static amalgamation.
2
3use core::ffi::c_char;
4use core::ffi::c_int;
5use core::ffi::c_uint;
6use core::ffi::c_void;
7
8pub type MiProfWriteFun = unsafe extern "C" fn(*mut c_void, *const c_char, usize);
9
10/// Mirrors `MI_PROF_STAT_VERSION` in `include/mimalloc/profile.h`.
11pub const MI_PROF_STAT_VERSION: c_int = 3;
12
13/// Mirrors `MI_DHAT_STATS_VERSION` in `include/mimalloc/dhat.h`.
14pub const MI_DHAT_STATS_VERSION: c_int = 1;
15
16/// Mirrors `mi_dhat_stats_t` (include/mimalloc/dhat.h) field-for-field.
17#[repr(C)]
18#[allow(non_camel_case_types)]
19pub struct mi_dhat_stats_t {
20 pub size: usize,
21 pub version: c_int,
22 pub enabled: bool,
23 pub incomplete: bool,
24 pub total_bytes: u64,
25 pub total_blocks: u64,
26 pub live_bytes: u64,
27 pub live_blocks: u64,
28 pub peak_bytes: u64,
29 pub peak_blocks: u64,
30 pub dropped: u64,
31 pub internal_bytes: u64,
32}
33
34/// Mirrors `mi_prof_stats_t` (include/mimalloc/profile.h) field-for-field.
35#[repr(C)]
36#[allow(non_camel_case_types)]
37pub struct mi_prof_stats_t {
38 pub size: usize,
39 pub version: c_int,
40 pub enabled: bool,
41 pub accum: bool,
42 pub sample_rate: usize,
43 pub live_samples: usize,
44 pub live_bytes: usize,
45 pub accum_samples: usize,
46 pub accum_bytes: usize,
47 pub unique_stacks: usize,
48 pub arena_committed: usize,
49 pub stack_table_overflows: usize,
50 /// v2. Mirrors `mi_prof_stats_t.dropped_samples`: count of ALL dropped
51 /// samples (record-alloc failure, stack-intern failure, including the
52 /// `MI_PROF_STACK_CAP` cap); `stack_table_overflows` is a subset, so
53 /// `dropped_samples >= stack_table_overflows` always.
54 pub dropped_samples: usize,
55 /// v3. Allocator-level ("ground truth") counters read from `mi_stats_get()`.
56 /// Unlike every field above, these are exact rather than sampled.
57 pub heap_committed: usize,
58 pub heap_reserved: usize,
59 pub heap_malloc_requested: usize,
60 pub heap_pages: usize,
61 pub heap_pages_abandoned: usize,
62 pub heap_count: usize,
63 /// Live thread-local heaps. The main thread's statically-initialized theap
64 /// is not counted, so a single-threaded process reports 0.
65 pub theap_count: usize,
66 pub heap_purged: usize,
67 /// True when the C library was built with `MI_STAT >= 2`. `heap_malloc_requested`
68 /// is only maintained at that level; a default release build reports 0.
69 pub heap_stats_detailed: bool,
70}
71
72/// Mirrors `MI_PROF_CONFIG_VERSION` in `include/mimalloc/profile.h`.
73pub const MI_PROF_CONFIG_VERSION: c_int = 1;
74
75/// Mirrors `MI_PROF_FORMAT_TEXT` / `MI_PROF_FORMAT_PROTO` (include/mimalloc/profile.h).
76pub const MI_PROF_FORMAT_TEXT: c_int = 0;
77pub const MI_PROF_FORMAT_PROTO: c_int = 1;
78
79/// Mirrors `mi_prof_config_mode_t`'s `MI_PROF_CONFIG_FALLBACK` /
80/// `MI_PROF_CONFIG_OVERRIDE` (include/mimalloc/profile.h).
81pub const MI_PROF_CONFIG_FALLBACK: c_int = 0;
82pub const MI_PROF_CONFIG_OVERRIDE: c_int = 1;
83
84/// Mirrors `mi_prof_config_t` (include/mimalloc/profile.h) field-for-field.
85#[repr(C)]
86#[allow(non_camel_case_types)]
87pub struct mi_prof_config_t {
88 pub size: usize,
89 pub version: c_int,
90 pub mode: c_int, // mi_prof_config_mode_t
91 pub sample_interval: usize,
92 pub max_profiler_bytes: usize,
93 pub seed: u64,
94 pub accum: bool,
95 pub max_stack_depth: usize,
96 pub dump_at_exit: *const c_char,
97 pub dump_format: c_int,
98}
99
100/// Mirrors `mi_prof_sample_info_t` (include/mimalloc/profile.h) field-for-field.
101#[repr(C)]
102#[allow(non_camel_case_types)]
103pub struct mi_prof_sample_info_t {
104 pub stack: *const *const c_void,
105 pub depth: usize,
106 pub live_objects: usize,
107 pub live_bytes: usize,
108 pub accum_objects: usize,
109 pub accum_bytes: usize,
110}
111
112#[allow(non_camel_case_types)]
113pub type mi_prof_visit_fun =
114 unsafe extern "C" fn(info: *const mi_prof_sample_info_t, arg: *mut c_void) -> bool;
115
116/// Opaque handle for `mi_prof_snapshot_t`; the profiler never hands out a
117/// value, only a pointer, so this type is never constructed on the Rust side.
118#[allow(non_camel_case_types)]
119pub enum mi_prof_snapshot_t {}
120
121/// Mirrors `mi_prof_module_info_t` (include/mimalloc/profile.h) field-for-field.
122#[repr(C)]
123#[allow(non_camel_case_types)]
124pub struct mi_prof_module_info_t {
125 pub path: *const c_char,
126 pub base: usize,
127 pub size: usize,
128}
129
130#[allow(non_camel_case_types)]
131pub type mi_prof_module_visit_fun =
132 unsafe extern "C" fn(info: *const mi_prof_module_info_t, arg: *mut c_void) -> bool;
133
134/// Opaque handle for `mi_heap_t` (issue #269, Bun parity P4): only ever seen behind a
135/// pointer here (mi_heap_get_seq), so this type is never constructed on the Rust side.
136#[allow(non_camel_case_types)]
137pub enum mi_heap_t {}
138
139/// Mirrors `mi_purge_holes_stats_t` (issue #272, Bun parity P7b): what page hole purging
140/// actually reclaimed, process wide. Field order and types must match `mimalloc.h` exactly.
141#[repr(C)]
142#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
143pub struct MiPurgeHolesStats {
144 pub purged_bytes: usize,
145 pub purged_blocks: usize,
146 pub purged_bytes_total: usize,
147 pub discard_calls: usize,
148 pub reuse_calls: usize,
149 pub pages_freed: usize,
150 pub ineligible_pages: usize,
151 pub ineligible_bytes: usize,
152 pub ineligible_free_bytes: usize,
153 pub unformed_bytes: usize,
154 pub unformed_bytes_total: usize,
155 pub unformed_discard_calls: usize,
156 pub unformed_reuse_calls: usize,
157 pub pages_skipped: usize,
158 pub blocks_visited: usize,
159 pub full_sweeps: usize,
160}
161
162/// Mirrors `mi_purge_flags_t` (include/mimalloc.h, issue #366). A plain C enum: the
163/// underlying type is `int` on every ABI mimalloc targets.
164#[allow(non_camel_case_types)]
165pub type mi_purge_flags_t = c_int;
166/// `MI_PURGE_FORCE` (`mi_purge_flags_t`, issue #366): ignore `purge_delay` / hole-purge
167/// pacing, and let a claimed sweep run to completion (ignoring `park_reclaim`).
168pub const MI_PURGE_FORCE: mi_purge_flags_t = 1;
169
170/// `MI_PURGE_OK` (issue #366): every registered thread was reached.
171pub const MI_PURGE_OK: c_int = 0;
172/// `MI_PURGE_PARTIAL` (issue #366): some owners pending (see the report); everything
173/// reachable was purged.
174pub const MI_PURGE_PARTIAL: c_int = 1;
175/// `MI_PURGE_BUSY` (issue #366): another purge is in flight, or this is a re-entrant call;
176/// nothing was done.
177pub const MI_PURGE_BUSY: c_int = 2;
178
179/// Mirrors `mi_purge_all_report_t` (include/mimalloc.h, issue #366): what `mi_purge_all_ex`
180/// returned to the OS and which threads it could not reach. Field order and types must
181/// match `mimalloc.h` exactly (checked by tests/t19_layout.rs).
182#[repr(C)]
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
184#[allow(non_camel_case_types)]
185pub struct mi_purge_all_report_t {
186 /// Bytes returned to the OS by the arena passes.
187 pub arena_bytes: usize,
188 /// Bytes returned by hole purging (every swept theap + abandoned pages).
189 pub hole_bytes: usize,
190 /// tlds claimed and swept by this call (the caller included).
191 pub theaps_swept: usize,
192 /// Registered tlds not reached within `wait_ms`.
193 pub theaps_pending: usize,
194 /// Pre-fork tlds of vanished threads, never touched.
195 pub theaps_orphaned: usize,
196 /// Built with `MI_OWNER_GATE` (configuration, not completion).
197 pub gated: bool,
198 /// `theaps_pending == 0 && theaps_orphaned == 0`.
199 pub complete: bool,
200}
201
202// ---------------------------------------------------------------------------------------
203// include/mimalloc-stats.h -- exact allocator statistics (upstream API)
204// ---------------------------------------------------------------------------------------
205
206/// Mirrors `MI_STAT_VERSION` in `include/mimalloc-stats.h`.
207pub const MI_STAT_VERSION: usize = 5;
208
209/// Mirrors `MI_BIN_HUGE` in `include/mimalloc-stats.h`; the bin arrays below hold
210/// `MI_BIN_HUGE + 1` entries.
211pub const MI_BIN_HUGE: usize = 73;
212
213/// Mirrors `MI_CBIN_COUNT` (`mi_chunkbin_t`) in `include/mimalloc-stats.h`.
214pub const MI_CBIN_COUNT: usize = 6;
215
216/// Mirrors `mi_stat_count_t`: a quantity tracked over time.
217#[repr(C)]
218#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
219#[allow(non_camel_case_types)]
220pub struct mi_stat_count_t {
221 /// Total ever allocated.
222 pub total: i64,
223 /// Peak simultaneous value.
224 pub peak: i64,
225 /// Value right now.
226 pub current: i64,
227}
228
229/// Mirrors `mi_stat_counter_t`: a monotonically increasing counter.
230#[repr(C)]
231#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
232#[allow(non_camel_case_types)]
233pub struct mi_stat_counter_t {
234 /// Total count.
235 pub total: i64,
236}
237
238/// Mirrors `mi_stats_t` (include/mimalloc-stats.h) field-for-field, including the
239/// reserved-for-future-use arrays and the three size-segregated bin arrays.
240///
241/// This is upstream's struct, unchanged by the fork -- in particular it carries **no**
242/// hole-purging or idle-sweep gauges. Those live in [`MiPurgeHolesStats`], because the
243/// sweep also covers pages no heap owns and `mi_stats_t` cannot grow (it is embedded in
244/// a theap, at the meta-allocator's 8 KB block limit).
245///
246/// Field order, offsets and total size are checked against the C library at test time by
247/// `tests/t19_layout.rs` (see `layout_probe.c`), which walks the header's own
248/// `MI_STAT_FIELDS` list -- so an upstream reordering fails loudly instead of silently
249/// reading the wrong counter.
250#[repr(C)]
251#[derive(Clone, Copy, Debug)]
252#[allow(non_camel_case_types)]
253pub struct mi_stats_t {
254 /// `sizeof(mi_stats_t)` as the C library sees it.
255 pub size: usize,
256 /// [`MI_STAT_VERSION`] as the C library sees it.
257 pub version: usize,
258 /// Count of mimalloc pages.
259 pub pages: mi_stat_count_t,
260 /// Reserved memory bytes.
261 pub reserved: mi_stat_count_t,
262 /// Committed bytes.
263 pub committed: mi_stat_count_t,
264 /// Reset bytes.
265 pub reset: mi_stat_counter_t,
266 /// Purged bytes.
267 pub purged: mi_stat_counter_t,
268 /// Committed memory inside pages.
269 pub page_committed: mi_stat_count_t,
270 /// Abandoned page count.
271 pub pages_abandoned: mi_stat_count_t,
272 /// Number of threads.
273 pub threads: mi_stat_count_t,
274 /// Allocated bytes `<= MI_LARGE_OBJ_SIZE_MAX`.
275 pub malloc_normal: mi_stat_count_t,
276 /// Allocated bytes in huge pages.
277 pub malloc_huge: mi_stat_count_t,
278 /// Bytes the application actually asked for; only maintained at `MI_STAT >= 2`.
279 pub malloc_requested: mi_stat_count_t,
280 /// `mmap`/`VirtualAlloc` calls.
281 pub mmap_calls: mi_stat_counter_t,
282 /// Commit calls.
283 pub commit_calls: mi_stat_counter_t,
284 /// Reset calls.
285 pub reset_calls: mi_stat_counter_t,
286 /// Purge calls.
287 pub purge_calls: mi_stat_counter_t,
288 /// Number of memory arenas.
289 pub arena_count: mi_stat_counter_t,
290 /// Number of blocks `<= MI_LARGE_OBJ_SIZE_MAX`.
291 pub malloc_normal_count: mi_stat_counter_t,
292 /// Number of huge blocks.
293 pub malloc_huge_count: mi_stat_counter_t,
294 /// Number of allocations with guard pages.
295 pub malloc_guarded_count: mi_stat_counter_t,
296 /// Internal: arena rollbacks.
297 pub arena_rollback_count: mi_stat_counter_t,
298 /// Internal: arena purges.
299 pub arena_purges: mi_stat_counter_t,
300 /// Internal: page extensions.
301 pub pages_extended: mi_stat_counter_t,
302 /// Internal: retired pages.
303 pub pages_retire: mi_stat_counter_t,
304 /// Internal: total pages searched for a fresh page.
305 pub page_searches: mi_stat_counter_t,
306 /// Internal: searched count for a fresh page.
307 pub page_searches_count: mi_stat_counter_t,
308 /// v1/v2 only; always zero on this v3 line.
309 pub segments: mi_stat_count_t,
310 /// v1/v2 only; always zero on this v3 line.
311 pub segments_abandoned: mi_stat_count_t,
312 /// v1/v2 only; always zero on this v3 line.
313 pub segments_cache: mi_stat_count_t,
314 /// v1/v2 only; always zero on this v3 line.
315 pub _segments_reserved: mi_stat_count_t,
316 /// v3 only: first-class heaps.
317 pub heaps: mi_stat_count_t,
318 /// v3 only: thread-local heaps (`mi_theap_t`).
319 pub theaps: mi_stat_count_t,
320 /// v3 only: pages reclaimed on allocation.
321 pub pages_reclaim_on_alloc: mi_stat_counter_t,
322 /// v3 only: pages reclaimed on free.
323 pub pages_reclaim_on_free: mi_stat_counter_t,
324 /// v3 only: full pages re-abandoned.
325 pub pages_reabandon_full: mi_stat_counter_t,
326 /// v3 only: busy waits while unabandoning a page.
327 pub pages_unabandon_busy_wait: mi_stat_counter_t,
328 /// v3 only: waits while deleting a heap.
329 pub heaps_delete_wait: mi_stat_counter_t,
330 /// Upstream's future-extension padding; do not read.
331 pub _stat_reserved: [mi_stat_count_t; 4],
332 /// Upstream's future-extension padding; do not read.
333 pub _stat_counter_reserved: [mi_stat_counter_t; 4],
334 /// Allocation per size bin.
335 pub malloc_bins: [mi_stat_count_t; MI_BIN_HUGE + 1],
336 /// Pages allocated per size bin.
337 pub page_bins: [mi_stat_count_t; MI_BIN_HUGE + 1],
338 /// Chunks per page size (`mi_chunkbin_t`).
339 pub chunk_bins: [mi_stat_count_t; MI_CBIN_COUNT],
340}
341
342/// Mirrors `mi_subproc_id_t` (include/mimalloc.h): an abstract, pointer-sized handle.
343#[repr(C)]
344#[derive(Clone, Copy, Debug, PartialEq, Eq)]
345#[allow(non_camel_case_types)]
346pub struct mi_subproc_id_t {
347 /// The opaque identifier. Only ever produced by `mi_subproc_main`/`mi_subproc_current`.
348 pub _mi_subproc_id: *mut c_void,
349}
350
351/// Mirrors `mi_output_fun` (include/mimalloc.h): mimalloc's text output sink.
352#[allow(non_camel_case_types)]
353pub type mi_output_fun = unsafe extern "C" fn(msg: *const c_char, arg: *mut c_void);
354
355// ---------------------------------------------------------------------------------------
356// include/mimalloc/memory-events.h -- allocation-change accounting (always compiled in)
357// ---------------------------------------------------------------------------------------
358
359/// Mirrors `MI_MEMORY_SNAPSHOT_VERSION` in `include/mimalloc/memory-events.h`.
360pub const MI_MEMORY_SNAPSHOT_VERSION: c_int = 1;
361
362/// Mirrors `mi_memory_change_kind_t`. Declared as a plain `c_int` rather than a Rust
363/// `enum` on purpose: the value is produced by C, and materialising an out-of-range
364/// discriminant into a `#[repr(i32)]` enum would be undefined behaviour.
365#[allow(non_camel_case_types)]
366pub type mi_memory_change_kind_t = c_int;
367
368/// Mirrors `MI_MEMORY_ALLOCATE`.
369pub const MI_MEMORY_ALLOCATE: mi_memory_change_kind_t = 0;
370/// Mirrors `MI_MEMORY_FREE`.
371pub const MI_MEMORY_FREE: mi_memory_change_kind_t = 1;
372/// Mirrors `MI_MEMORY_RESIZE`.
373pub const MI_MEMORY_RESIZE: mi_memory_change_kind_t = 2;
374/// Mirrors `MI_MEMORY_CHANGE_COUNT`: the number of callback slots, not a kind.
375pub const MI_MEMORY_CHANGE_COUNT: usize = 3;
376
377/// Mirrors `mi_memory_change_t` (include/mimalloc/memory-events.h) field-for-field.
378#[repr(C)]
379#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
380#[allow(non_camel_case_types)]
381pub struct mi_memory_change_t {
382 /// One of [`MI_MEMORY_ALLOCATE`] / [`MI_MEMORY_FREE`] / [`MI_MEMORY_RESIZE`].
383 pub kind: mi_memory_change_kind_t,
384 /// Tracked global live usable bytes after this operation.
385 pub total_bytes: u64,
386 /// Signed change in tracked live usable bytes caused by this operation.
387 pub delta_bytes: i64,
388 /// Caller-requested size for allocation and resize; zero for free.
389 pub request_size: u64,
390}
391
392/// Mirrors `mi_memory_change_fun` (include/mimalloc/memory-events.h).
393#[allow(non_camel_case_types)]
394pub type mi_memory_change_fun =
395 unsafe extern "C" fn(change: *const mi_memory_change_t, arg: *mut c_void);
396
397/// Mirrors `mi_memory_callbacks_t` (include/mimalloc/memory-events.h): one handler and
398/// one caller-owned `arg` per [`mi_memory_change_kind_t`], indexed by the kind.
399#[repr(C)]
400#[derive(Clone, Copy)]
401#[allow(non_camel_case_types)]
402pub struct mi_memory_callbacks_t {
403 /// Handler per change kind; `None` leaves that kind unobserved.
404 pub handlers: [Option<mi_memory_change_fun>; MI_MEMORY_CHANGE_COUNT],
405 /// Caller-owned context per change kind, passed back to the matching handler.
406 pub args: [*mut c_void; MI_MEMORY_CHANGE_COUNT],
407}
408
409/// Mirrors `mi_memory_snapshot_t` (include/mimalloc/memory-events.h) field-for-field.
410#[repr(C)]
411#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
412#[allow(non_camel_case_types)]
413pub struct mi_memory_snapshot_t {
414 /// `sizeof(mi_memory_snapshot_t)`; fill in before calling `mi_memory_snapshot`.
415 pub size: usize,
416 /// [`MI_MEMORY_SNAPSHOT_VERSION`]; fill in before calling `mi_memory_snapshot`.
417 pub version: c_int,
418 /// Tracked live usable bytes right now.
419 pub live_bytes: u64,
420 /// Cumulative usable bytes ever allocated.
421 pub accum_bytes: u64,
422 /// Tracked live allocation count right now.
423 pub live_count: u64,
424 /// Cumulative count of successful allocate events.
425 pub accum_count: u64,
426}
427
428/// Mirrors `mi_memory_allocation_visit_fun` (include/mimalloc/memory-events.h).
429#[allow(non_camel_case_types)]
430pub type mi_memory_allocation_visit_fun =
431 unsafe extern "C" fn(allocation: *mut c_void, usable_size: usize, arg: *mut c_void) -> bool;
432
433// ---------------------------------------------------------------------------------------
434// include/mimalloc.h -- mi_option_t
435// ---------------------------------------------------------------------------------------
436
437/// Mirrors `mi_option_t` (include/mimalloc.h).
438///
439/// **Positional, and silently wrong if it drifts.** This fork inserts thirteen
440/// enumerators (`mi_option_prof*`, `mi_option_memory_events`, `mi_option_purge_zeroes`,
441/// `mi_option_scavenger`, `mi_option_purge_holes*`) at indices 47..=59, immediately
442/// before `_mi_option_last` -- so a mirror copied from upstream mimalloc would set a
443/// *different* option than the caller named, with no diagnostic. Every value below is
444/// checked against the C enum at test time by `tests/t19_layout.rs`, and the ordering is
445/// checked against `include/mimalloc.h` by `ci/check_rust_surface.py`.
446#[allow(non_camel_case_types)]
447pub type mi_option_t = c_int;
448
449macro_rules! mi_options {
450 ($($(#[$attr:meta])* $name:ident = $value:expr;)*) => {
451 $(
452 $(#[$attr])*
453 #[allow(non_upper_case_globals)]
454 pub const $name: mi_option_t = $value;
455 )*
456 /// Every enumerator of `mi_option_t` up to and including `_mi_option_last`, in C
457 /// declaration order, as `(name, value)` pairs. Used by `tests/t19_layout.rs` to
458 /// check this mirror against the C enum, and by `ci/check_rust_surface.py` to
459 /// check it against `include/mimalloc.h`.
460 pub const MI_OPTIONS_IN_ORDER: &[(&str, mi_option_t)] = &[$((stringify!($name), $name)),*];
461 };
462}
463
464mi_options! {
465 /// Print error messages.
466 mi_option_show_errors = 0;
467 /// Print statistics on termination.
468 mi_option_show_stats = 1;
469 /// Print verbose messages.
470 mi_option_verbose = 2;
471 /// Deprecated; kept for numbering.
472 mi_option_deprecated_eager_commit = 3;
473 /// Eagerly commit arena memory.
474 mi_option_arena_eager_commit = 4;
475 /// Purge decommits rather than resets.
476 mi_option_purge_decommits = 5;
477 /// Allow large (2 MiB) OS pages.
478 mi_option_allow_large_os_pages = 6;
479 /// Reserve N huge OS pages at startup.
480 mi_option_reserve_huge_os_pages = 7;
481 /// Reserve huge OS pages at a specific NUMA node.
482 mi_option_reserve_huge_os_pages_at = 8;
483 /// Reserve N KiB of OS memory at startup.
484 mi_option_reserve_os_memory = 9;
485 /// Deprecated; kept for numbering.
486 mi_option_deprecated_segment_cache = 10;
487 /// Deprecated; kept for numbering.
488 mi_option_deprecated_page_reset = 11;
489 /// Deprecated; kept for numbering.
490 mi_option_deprecated_abandoned_page_purge = 12;
491 /// Deprecated; kept for numbering.
492 mi_option_deprecated_segment_reset = 13;
493 /// Deprecated; kept for numbering.
494 mi_option_deprecated_eager_commit_delay = 14;
495 /// Milliseconds to delay purging.
496 mi_option_purge_delay = 15;
497 /// Number of NUMA nodes to use.
498 mi_option_use_numa_nodes = 16;
499 /// Refuse to allocate from the OS.
500 mi_option_disallow_os_alloc = 17;
501 /// Tag passed to the OS allocator.
502 mi_option_os_tag = 18;
503 /// Maximum error messages printed.
504 mi_option_max_errors = 19;
505 /// Maximum warning messages printed.
506 mi_option_max_warnings = 20;
507 /// Deprecated; kept for numbering.
508 mi_option_deprecated_max_segment_reclaim = 21;
509 /// Release all memory on process exit.
510 mi_option_destroy_on_exit = 22;
511 /// Bytes to reserve per new arena.
512 mi_option_arena_reserve = 23;
513 /// Multiplier on the arena purge delay.
514 mi_option_arena_purge_mult = 24;
515 /// Deprecated; kept for numbering.
516 mi_option_deprecated_purge_extend_delay = 25;
517 /// Refuse to allocate from arenas.
518 mi_option_disallow_arena_alloc = 26;
519 /// Retry count on OS out-of-memory.
520 mi_option_retry_on_oom = 27;
521 /// Deprecated; kept for numbering.
522 mi_option_deprecated_visit_abandoned = 28;
523 /// Minimum size for guard-page allocation.
524 mi_option_guarded_min = 29;
525 /// Maximum size for guard-page allocation.
526 mi_option_guarded_max = 30;
527 /// Place guard pages precisely after the block.
528 mi_option_guarded_precise = 31;
529 /// Sample every N-th allocation for guard pages.
530 mi_option_guarded_sample_rate = 32;
531 /// Seed for guard-page sampling.
532 mi_option_guarded_sample_seed = 33;
533 /// Collect generically every N-th generic allocation.
534 mi_option_generic_collect = 34;
535 /// Reclaim abandoned pages on free.
536 mi_option_page_reclaim_on_free = 35;
537 /// Number of full pages to retain per bin.
538 mi_option_page_full_retain = 36;
539 /// Candidate pages searched per allocation.
540 mi_option_page_max_candidates = 37;
541 /// Maximum virtual address bits to assume.
542 mi_option_max_vabits = 38;
543 /// Commit the page map eagerly.
544 mi_option_pagemap_commit = 39;
545 /// Commit page memory on demand.
546 mi_option_page_commit_on_demand = 40;
547 /// Maximum pages reclaimed at once.
548 mi_option_page_max_reclaim = 41;
549 /// Maximum cross-thread pages reclaimed at once.
550 mi_option_page_cross_thread_max_reclaim = 42;
551 /// Allow transparent huge pages.
552 mi_option_allow_thp = 43;
553 /// Smallest range worth purging.
554 mi_option_minimal_purge_size = 44;
555 /// Largest object served from an arena.
556 mi_option_arena_max_object_size = 45;
557 /// Treat arenas as NUMA local.
558 mi_option_arena_is_numa_local = 46;
559 /// **Fork addition.** Enable the allocation sampling profiler at process start
560 /// (`MIMALLOC_PROF`).
561 mi_option_prof = 47;
562 /// **Fork addition.** Average byte interval between profiler samples.
563 mi_option_prof_sample_rate = 48;
564 /// **Fork addition.** Maximum captured stack depth for the profiler.
565 mi_option_prof_bt_max = 49;
566 /// **Fork addition.** Keep cumulative profiler counters until `mi_prof_reset`.
567 mi_option_prof_accum = 50;
568 /// **Fork addition.** Profiler sampling PRNG seed; 0 = nondeterministic.
569 mi_option_prof_seed = 51;
570 /// **Fork addition.** Budget in bytes for profiler-internal arena memory.
571 mi_option_prof_max_bytes = 52;
572 /// **Fork addition.** Enable allocation-change accounting/callbacks
573 /// (`MIMALLOC_MEMORY_EVENTS`).
574 mi_option_memory_events = 53;
575 /// **Fork addition, dead since #80.** The slot is kept so nothing renumbers; setting
576 /// it parses but has no effect. Unrelated to `mi_option_purge_holes_eager_zero`.
577 mi_option_purge_zeroes = 54;
578 /// **Fork addition (Bun).** Run a background thread that purges scheduled arena memory.
579 mi_option_scavenger = 55;
580 /// **Fork addition (Bun).** Discard the memory of free blocks inside still-used pages
581 /// on `mi_on_thread_idle`.
582 mi_option_purge_holes = 56;
583 /// **Fork addition (Bun).** Zero a range before discarding it, so a mis-scoped discard
584 /// corrupts visibly.
585 mi_option_purge_holes_eager_zero = 57;
586 /// **Fork addition (Bun).** Minimum milliseconds between sweeps of one thread's heaps.
587 mi_option_purge_holes_min_interval = 58;
588 /// **Fork addition (Bun).** Every N-th sweep walks every page, ignoring the per-page
589 /// skip check; 0 disables.
590 mi_option_purge_holes_full_every = 59;
591 /// **Fork addition (Bun parity, #338).** Write a heap snapshot on process exit: 0 = off,
592 /// 1 = pages, 2 = pages + per-block free maps. Path from `MIMALLOC_SNAPSHOT_PATH`, else
593 /// `mimalloc-snapshot.<pid>.bin`.
594 mi_option_snapshot_on_exit = 60;
595 /// Sentinel: one past the last real option.
596 _mi_option_last = 61;
597}
598
599/// `MI_SNAPSHOT_BLOCKS` (include/mimalloc.h, #338): include per-block free bitmaps for the
600/// pages the calling thread owns.
601pub const MI_SNAPSHOT_BLOCKS: c_uint = 0x01;
602
603/// Deprecated upstream alias for [`mi_option_allow_large_os_pages`].
604#[allow(non_upper_case_globals)]
605pub const mi_option_large_os_pages: mi_option_t = mi_option_allow_large_os_pages;
606/// Deprecated upstream alias for [`mi_option_arena_eager_commit`].
607#[allow(non_upper_case_globals)]
608pub const mi_option_eager_region_commit: mi_option_t = mi_option_arena_eager_commit;
609/// Deprecated upstream alias for [`mi_option_purge_decommits`].
610#[allow(non_upper_case_globals)]
611pub const mi_option_reset_decommits: mi_option_t = mi_option_purge_decommits;
612/// Deprecated upstream alias for [`mi_option_purge_delay`].
613#[allow(non_upper_case_globals)]
614pub const mi_option_reset_delay: mi_option_t = mi_option_purge_delay;
615/// Deprecated upstream alias for [`mi_option_disallow_os_alloc`].
616#[allow(non_upper_case_globals)]
617pub const mi_option_limit_os_alloc: mi_option_t = mi_option_disallow_os_alloc;
618
619/// One entry of the ABI layout table published by [`mi_rs_layout_table`].
620#[repr(C)]
621#[derive(Clone, Copy)]
622pub struct MiRsLayoutEntry {
623 /// NUL-terminated static key, e.g. `sizeof:mi_stats_t` or `option:mi_option_prof`.
624 pub name: *const c_char,
625 /// The value the C compiler computed for that key.
626 pub value: usize,
627}
628
629unsafe extern "C" {
630 pub fn mi_malloc(size: usize) -> *mut c_void;
631 pub fn mi_zalloc(size: usize) -> *mut c_void;
632 pub fn mi_calloc(count: usize, size: usize) -> *mut c_void;
633 pub fn mi_realloc(p: *mut c_void, newsize: usize) -> *mut c_void;
634 pub fn mi_free(p: *mut c_void);
635 pub fn mi_malloc_aligned(size: usize, alignment: usize) -> *mut c_void;
636 pub fn mi_zalloc_aligned(size: usize, alignment: usize) -> *mut c_void;
637 pub fn mi_realloc_aligned(p: *mut c_void, newsize: usize, alignment: usize) -> *mut c_void;
638 // Zeroing reallocation (issue #83). These grow a block AND zero the new tail, which
639 // `GlobalAlloc` cannot express -- it has no `grow_zeroed` -- so a Rust caller would
640 // otherwise grow and `memset` by hand, redoing work mimalloc has already done.
641 //
642 // What they zero is NOT [old_requested, new): mimalloc measures from the block's
643 // old *usable* size, so the slack between requested and usable is left as-is. A
644 // block requested at 64 bytes may be usable to 80, and growing to 70 is served in
645 // place with nothing zeroed. See `prof::rezalloc` for the safe-wrapper docs.
646 pub fn mi_rezalloc(p: *mut c_void, newsize: usize) -> *mut c_void;
647 pub fn mi_recalloc(p: *mut c_void, newcount: usize, size: usize) -> *mut c_void;
648 pub fn mi_rezalloc_aligned(p: *mut c_void, newsize: usize, alignment: usize) -> *mut c_void;
649 pub fn mi_recalloc_aligned(
650 p: *mut c_void,
651 newcount: usize,
652 size: usize,
653 alignment: usize,
654 ) -> *mut c_void;
655 /// Grow in place only; returns NULL if the block cannot be extended without moving.
656 pub fn mi_expand(p: *mut c_void, newsize: usize) -> *mut c_void;
657 pub fn mi_usable_size(p: *const c_void) -> usize;
658 pub fn mi_dhat_start() -> bool;
659 pub fn mi_dhat_stop();
660 pub fn mi_dhat_is_enabled() -> bool;
661 pub fn mi_dhat_stats_get(stats: *mut mi_dhat_stats_t) -> bool;
662 pub fn mi_dhat_dump(path: *const c_char) -> bool;
663 pub fn mi_prof_start(sample_rate: usize) -> bool;
664 pub fn mi_prof_start_seeded(sample_rate: usize, seed: u64) -> bool;
665 pub fn mi_prof_start_ex(config: *const mi_prof_config_t) -> bool;
666 pub fn mi_prof_stop();
667 pub fn mi_prof_is_enabled() -> bool;
668 pub fn mi_prof_dump(path: *const c_char) -> bool;
669 pub fn mi_prof_dump_writer(write: Option<MiProfWriteFun>, arg: *mut c_void) -> bool;
670 /// profile.proto (google/pprof) writer: same sample/period/mapping semantics as
671 /// `mi_prof_dump`/`mi_prof_dump_writer` but encoded as an uncompressed, binary
672 /// pprof Profile message instead of the legacy "heap profile:" text.
673 pub fn mi_prof_dump_proto(path: *const c_char) -> bool;
674 pub fn mi_prof_dump_proto_writer(write: Option<MiProfWriteFun>, arg: *mut c_void) -> bool;
675 pub fn mi_prof_reset();
676 pub fn mi_prof_debug_stats(records: *mut usize, bytes: *mut usize, unique_stacks: *mut usize);
677 pub fn mi_prof_stats_get(stats: *mut mi_prof_stats_t) -> bool;
678 pub fn mi_prof_visit(visitor: mi_prof_visit_fun, arg: *mut c_void) -> bool;
679 pub fn mi_prof_snapshot_new() -> *mut mi_prof_snapshot_t;
680 pub fn mi_prof_snapshot_visit(
681 snap: *const mi_prof_snapshot_t,
682 visitor: mi_prof_visit_fun,
683 arg: *mut c_void,
684 ) -> bool;
685 pub fn mi_prof_snapshot_free(snap: *mut mi_prof_snapshot_t);
686 /// Structured module (mapping) enumeration, e.g. to build pprof Mapping entries
687 /// yourself. No profiler lock is taken: module lists are OS-owned, not part of
688 /// the sampled-allocation table. `info` (and `info->path`) are valid only for
689 /// the duration of the callback.
690 pub fn mi_prof_modules_visit(visitor: mi_prof_module_visit_fun, arg: *mut c_void) -> bool;
691
692 /// Mirrors `mi_unwrapped_malloc` (include/mimalloc/memory-events.h): backed
693 /// directly by the raw OS layer, never by the hooked `mi_malloc` family.
694 /// See that header's "Stable public unwrapped instrumentation allocation
695 /// path" comment for the full contract.
696 pub fn mi_unwrapped_malloc(size: usize, alignment: usize) -> *mut c_void;
697 /// Mirrors `mi_unwrapped_free` (include/mimalloc/memory-events.h).
698 pub fn mi_unwrapped_free(p: *mut c_void);
699 /// Mirrors `mi_unwrapped_realloc` (include/mimalloc/memory-events.h).
700 pub fn mi_unwrapped_realloc(p: *mut c_void, new_size: usize, alignment: usize) -> *mut c_void;
701
702 /// Live per-heap -> per-page -> (optional) per-block JSON snapshot (issue #269, Bun
703 /// parity P4). Backs Bun's shipped `bun:jsc` `heapStats({dump:true|"blocks"})`. Returns
704 /// NULL on allocation failure; a non-NULL result is `mi_malloc`-family memory the
705 /// caller must free with `mi_free` (see `prof::heap_dump_json` for the safe wrapper).
706 pub fn mi_heap_dump_json(include_blocks: bool, hash_addresses: bool) -> *mut c_char;
707 /// Mirrors `mi_heap_dump_json_ex`: in owner-gated builds, retry an incomplete
708 /// capture from a clean boundary for up to `wait_ms`.
709 pub fn mi_heap_dump_json_ex(
710 include_blocks: bool,
711 hash_addresses: bool,
712 wait_ms: usize,
713 ) -> *mut c_char;
714 /// Mirrors `mi_heap_get_seq` (include/mimalloc-stats.h): the monotonic sequence
715 /// number assigned to `heap` at creation, or 0 for a NULL heap.
716 pub fn mi_heap_get_seq(heap: *mut mi_heap_t) -> usize;
717
718 /// Mirrors `mi_heap_snapshot` (include/mimalloc.h; issue #338, Bun parity): write a
719 /// binary heap snapshot (format version 1, byte-identical to oven-sh/mimalloc's) to a
720 /// CRT file descriptor. `flags` is 0 or `MI_SNAPSHOT_BLOCKS`. Returns 0, or -1 on a write
721 /// error. sys-only: on Windows the fd is a CRT descriptor, not a HANDLE -- use
722 /// `heap::snapshot_to_file` from safe code.
723 pub fn mi_heap_snapshot(fd: c_int, flags: c_uint) -> c_int;
724 /// Mirrors `mi_heap_snapshot_to_file` (include/mimalloc.h; issue #338): open `path`
725 /// (create/truncate), write the snapshot, close. Returns 0, or -1 on open/write error.
726 pub fn mi_heap_snapshot_to_file(path: *const c_char, flags: c_uint) -> c_int;
727
728 /// Mirrors `mi_on_thread_idle` (issue #272, Bun parity P7a).
729 pub fn mi_on_thread_idle();
730 /// Mirrors `mi_on_thread_idle_start`; `false` means nothing was handed off and
731 /// `mi_on_thread_idle_end` must NOT be called.
732 pub fn mi_on_thread_idle_start() -> bool;
733 /// Mirrors `mi_on_thread_idle_end`.
734 pub fn mi_on_thread_idle_end();
735 /// Mirrors `mi_scavenger_stop` (issue #272).
736 pub fn mi_scavenger_stop();
737 /// Mirrors `mi_purge_holes_stats_get` (issue #272, Bun parity P7b).
738 pub fn mi_purge_holes_stats_get(stats: *mut MiPurgeHolesStats);
739
740 /// Mirrors `mi_purge_holes_report` (issue #272, Bun parity P7b): prints, per size
741 /// class, the free bytes hole purging could NOT discard. Read-only; purges nothing.
742 pub fn mi_purge_holes_report();
743
744 /// Mirrors `mi_purge_all_ex` (include/mimalloc.h, issue #366): process-wide eager purge
745 /// from any thread. Returns `MI_PURGE_OK`, `MI_PURGE_PARTIAL` or `MI_PURGE_BUSY`.
746 /// `wait_ms` bounds owner-acquisition waiting ONLY -- not a claimed thread's sweep and
747 /// not its purge syscalls. `report` may be NULL.
748 pub fn mi_purge_all_ex(
749 flags: mi_purge_flags_t,
750 wait_ms: usize,
751 report: *mut mi_purge_all_report_t,
752 ) -> c_int;
753 /// Mirrors `mi_purge_all` (issue #366):
754 /// `mi_purge_all_ex(force ? MI_PURGE_FORCE : 0, 100, NULL)`.
755 pub fn mi_purge_all(force: bool);
756
757 // ---- include/mimalloc.h: options ----
758 /// Mirrors `mi_option_is_enabled`.
759 pub fn mi_option_is_enabled(option: mi_option_t) -> bool;
760 /// Mirrors `mi_option_enable`.
761 pub fn mi_option_enable(option: mi_option_t);
762 /// Mirrors `mi_option_disable`.
763 pub fn mi_option_disable(option: mi_option_t);
764 /// Mirrors `mi_option_set_enabled`.
765 pub fn mi_option_set_enabled(option: mi_option_t, enable: bool);
766 /// Mirrors `mi_option_set_enabled_default`.
767 pub fn mi_option_set_enabled_default(option: mi_option_t, enable: bool);
768 /// Mirrors `mi_option_get`.
769 pub fn mi_option_get(option: mi_option_t) -> core::ffi::c_long;
770 /// Mirrors `mi_option_get_clamp`.
771 pub fn mi_option_get_clamp(
772 option: mi_option_t,
773 min: core::ffi::c_long,
774 max: core::ffi::c_long,
775 ) -> core::ffi::c_long;
776 /// Mirrors `mi_option_get_size`.
777 pub fn mi_option_get_size(option: mi_option_t) -> usize;
778 /// Mirrors `mi_option_set`.
779 pub fn mi_option_set(option: mi_option_t, value: core::ffi::c_long);
780 /// Mirrors `mi_option_set_default`.
781 pub fn mi_option_set_default(option: mi_option_t, value: core::ffi::c_long);
782 /// Mirrors `mi_options_print_out`: prints every option's current value.
783 pub fn mi_options_print_out(out: Option<mi_output_fun>, arg: *mut c_void);
784
785 // ---- include/mimalloc-stats.h: exact statistics ----
786 /// Mirrors `mi_stats_get`: aggregated stats for the current subprocess and its heaps.
787 pub fn mi_stats_get(stats: *mut mi_stats_t) -> bool;
788 /// Mirrors `mi_stats_get_json`. With `buf == NULL` the result is `mi_malloc`-family
789 /// memory the caller must release with [`mi_free`].
790 pub fn mi_stats_get_json(buf_size: usize, buf: *mut c_char) -> *mut c_char;
791 /// Mirrors `mi_stats_as_json`: render an already-captured [`mi_stats_t`].
792 pub fn mi_stats_as_json(
793 stats: *mut mi_stats_t,
794 buf_size: usize,
795 buf: *mut c_char,
796 ) -> *mut c_char;
797 /// Mirrors `mi_stats_print_out`.
798 pub fn mi_stats_print_out(out: Option<mi_output_fun>, arg: *mut c_void);
799 /// Mirrors `mi_stats_get_bin_size`: the block size served by size bin `bin`.
800 pub fn mi_stats_get_bin_size(bin: usize) -> usize;
801 /// Mirrors `mi_heap_stats_get`.
802 pub fn mi_heap_stats_get(heap: *mut mi_heap_t, stats: *mut mi_stats_t) -> bool;
803 /// Mirrors `mi_heap_stats_get_json`.
804 pub fn mi_heap_stats_get_json(
805 heap: *mut mi_heap_t,
806 buf_size: usize,
807 buf: *mut c_char,
808 ) -> *mut c_char;
809 /// Mirrors `mi_heap_stats_print_out`.
810 pub fn mi_heap_stats_print_out(
811 heap: *mut mi_heap_t,
812 out: Option<mi_output_fun>,
813 arg: *mut c_void,
814 );
815 /// Mirrors `mi_heap_stats_merge_to_subproc`: fold a heap's stats into its subprocess
816 /// and clear the heap's own.
817 pub fn mi_heap_stats_merge_to_subproc(heap: *mut mi_heap_t);
818 /// Mirrors `mi_subproc_stats_get`.
819 pub fn mi_subproc_stats_get(subproc_id: mi_subproc_id_t, stats: *mut mi_stats_t) -> bool;
820 /// Mirrors `mi_subproc_stats_get_exclusive`: the subprocess's own stats, without
821 /// aggregating its heaps.
822 pub fn mi_subproc_stats_get_exclusive(
823 subproc_id: mi_subproc_id_t,
824 stats: *mut mi_stats_t,
825 ) -> bool;
826 /// Mirrors `mi_subproc_stats_get_json`.
827 pub fn mi_subproc_stats_get_json(
828 subproc_id: mi_subproc_id_t,
829 buf_size: usize,
830 buf: *mut c_char,
831 ) -> *mut c_char;
832 /// Mirrors `mi_subproc_stats_print_out`.
833 pub fn mi_subproc_stats_print_out(
834 subproc_id: mi_subproc_id_t,
835 out: Option<mi_output_fun>,
836 arg: *mut c_void,
837 );
838 /// Mirrors `mi_subproc_heap_stats_print_out`: the subprocess and each of its heaps,
839 /// printed separately.
840 pub fn mi_subproc_heap_stats_print_out(
841 subproc_id: mi_subproc_id_t,
842 out: Option<mi_output_fun>,
843 arg: *mut c_void,
844 );
845 /// Mirrors `mi_subproc_main`: the process-wide default subprocess.
846 pub fn mi_subproc_main() -> mi_subproc_id_t;
847 /// Mirrors `mi_subproc_current`: the subprocess this thread belongs to.
848 pub fn mi_subproc_current() -> mi_subproc_id_t;
849
850 // ---- include/mimalloc/memory-events.h ----
851 /// Mirrors `mi_memory_tracking_set_enabled`; returns the previous state. An explicit
852 /// call is always authoritative over the `MIMALLOC_MEMORY_EVENTS` environment read.
853 pub fn mi_memory_tracking_set_enabled(enabled: bool) -> bool;
854 /// Mirrors `mi_memory_tracking_is_enabled`.
855 pub fn mi_memory_tracking_is_enabled() -> bool;
856 /// Mirrors `mi_memory_set_callbacks`. `callbacks == NULL` clears the table. The `arg`
857 /// pointers are caller-owned and must stay valid until replaced or cleared.
858 pub fn mi_memory_set_callbacks(callbacks: *const mi_memory_callbacks_t) -> bool;
859 /// Mirrors `mi_memory_snapshot`; fill `size`/`version` in before calling.
860 pub fn mi_memory_snapshot(out: *mut mi_memory_snapshot_t) -> bool;
861 /// Mirrors `mi_memory_visit_live_allocations`. Best effort, not a consistent global
862 /// snapshot; the visitor must not allocate, free, or reenter mimalloc.
863 pub fn mi_memory_visit_live_allocations(
864 visitor: mi_memory_allocation_visit_fun,
865 arg: *mut c_void,
866 ) -> bool;
867
868 /// Publishes what the C compiler laid out for every mirrored type, constant and
869 /// option in this module. Defined by `layout_probe.c` (a rust/-side verification
870 /// artifact, not part of the C library) and consumed by `tests/t19_layout.rs`.
871 pub fn mi_rs_layout_table(count: *mut usize) -> *const MiRsLayoutEntry;
872}