blob: 7cce9edd753969ebc4219d414bcbe7e4a4e77744 [file] [edit]
/* ----------------------------------------------------------------------------
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) { // see issue #1289
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);
}