mirror of
https://github.com/python/cpython.git
synced 2026-10-29 19:03:24 +08:00
* Update vendored mimalloc to v2.5.2 Upgrade mimalloc from v2.1.2 to v2.5.2 as-is from upstream. v2.1.2: https://github.com/microsoft/mimalloc/releases/tag/v2.1.2 v2.5.2: https://github.com/microsoft/mimalloc/releases/tag/v2.5.2 * Re-apply CPython-specific changes to mimalloc v2.5.2 Most of the previous downstream changes were upstreamed in mimalloc v2.1.3 - v2.1.7 (heap tags, _mi_heap_init/_mi_tld_init, MI_PRIM_THREAD_ID, sub-processes, heap walk optimizations, platform fixes) and are dropped. Remaining CPython-specific changes, all marked with a "CPython" comment: - QSBR delayed page freeing for free-threaded builds (gh-115103, gh-145615): mi_page_t/mi_heap_t fields and _PyMem_mi_* hooks in page.c, heap.c and segment.c. - debug_offset: preserve the first bytes of a PyObject (ob_tid, refcount fields) when filling freed or uninitialized memory in debug builds (gh-115491). - Atomic mi_block_t.next loads/stores to avoid TSAN reports (gh-129748). - Relax the owning thread assertion in _mi_free_delayed_block for free-threaded builds, since the GC traverses heaps of other threads. - Call _mi_heap_delayed_free_partial() in mi_heap_visit_blocks() (gh-112532). - Only count reclaimed pages up to MI_MEDIUM_OBJ_SIZE_MAX as a "right page" in mi_segment_reclaim (gh-115103). - Rename the weak _ZSt15get_new_handlerv symbol and disable the mi_is_in_heap_region debug warning. - Zero-initialize _mi_page_empty instead of a positional initializer. * Update the free-threaded build to mimalloc v2.5.2 changes mimalloc v2.1.4 replaced the list of abandoned segments with per-arena bitmaps and introduced sub-processes (mi_subproc_t) to keep abandoned segments of separate interpreters apart. Use them instead of the CPython-specific mi_abandoned_pool_t: - Each interpreter owns a mi_subproc_t created in init_interpreter(). - tstate_mimalloc_bind() sets tld->segments.subproc and uses the upstream _mi_heap_init() signature. - The GC and sys.getallocatedblocks() visit abandoned segments with _PyMem_mi_visit_abandoned_blocks(), the upstream cursor API without the mi_option_visit_abandoned requirement, while STW. * Update NEWS.d * fix MSAN * fix TSAN * fix test_cext * Disable THP * Address code review * Address code review * Address code review * nit * Address code review * Address code review
148 lines
6.4 KiB
C
148 lines
6.4 KiB
C
/* ----------------------------------------------------------------------------
|
|
Copyright (c) 2019-2023, 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.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/* -----------------------------------------------------------
|
|
The following functions are to reliably find the segment or
|
|
block that encompasses any pointer p (or NULL if it is not
|
|
in any of our segments).
|
|
We maintain a bitmap of all memory with 1 bit per MI_SEGMENT_SIZE (64MiB)
|
|
set to 1 if it contains the segment meta data.
|
|
----------------------------------------------------------- */
|
|
#include "mimalloc.h"
|
|
#include "mimalloc/internal.h"
|
|
#include "mimalloc/atomic.h"
|
|
|
|
// Reduce total address space to reduce .bss (due to the `mi_segment_map`)
|
|
#if (MI_INTPTR_SIZE > 4) && MI_TRACK_ASAN
|
|
#define MI_SEGMENT_MAP_MAX_ADDRESS (128*1024ULL*MI_GiB) // 128 TiB (see issue #881)
|
|
#elif (MI_INTPTR_SIZE > 4)
|
|
#define MI_SEGMENT_MAP_MAX_ADDRESS (48*1024ULL*MI_GiB) // 48 TiB
|
|
#else
|
|
#define MI_SEGMENT_MAP_MAX_ADDRESS (UINT32_MAX)
|
|
#endif
|
|
|
|
#define MI_SEGMENT_MAP_PART_SIZE (MI_INTPTR_SIZE*MI_KiB - 128) // 128 > sizeof(mi_memid_t) !
|
|
#define MI_SEGMENT_MAP_PART_BITS (8*MI_SEGMENT_MAP_PART_SIZE)
|
|
#define MI_SEGMENT_MAP_PART_ENTRIES (MI_SEGMENT_MAP_PART_SIZE / MI_INTPTR_SIZE)
|
|
#define MI_SEGMENT_MAP_PART_BIT_SPAN (MI_SEGMENT_ALIGN) // memory area covered by 1 bit
|
|
|
|
#if (MI_SEGMENT_MAP_PART_BITS < (MI_SEGMENT_MAP_MAX_ADDRESS / MI_SEGMENT_MAP_PART_BIT_SPAN)) // prevent overflow on 32-bit (issue #1017)
|
|
#define MI_SEGMENT_MAP_PART_SPAN (MI_SEGMENT_MAP_PART_BITS * MI_SEGMENT_MAP_PART_BIT_SPAN)
|
|
#else
|
|
#define MI_SEGMENT_MAP_PART_SPAN MI_SEGMENT_MAP_MAX_ADDRESS
|
|
#endif
|
|
|
|
#define MI_SEGMENT_MAP_MAX_PARTS ((MI_SEGMENT_MAP_MAX_ADDRESS / MI_SEGMENT_MAP_PART_SPAN) + 1)
|
|
|
|
// A part of the segment map.
|
|
typedef struct mi_segmap_part_s {
|
|
mi_memid_t memid;
|
|
_Atomic(uintptr_t) map[MI_SEGMENT_MAP_PART_ENTRIES];
|
|
} mi_segmap_part_t;
|
|
|
|
// Allocate parts on-demand to reduce .bss footprint
|
|
static _Atomic(mi_segmap_part_t*) mi_segment_map[MI_SEGMENT_MAP_MAX_PARTS]; // = { NULL, .. }
|
|
|
|
static mi_segmap_part_t* mi_segment_map_index_of(const mi_segment_t* segment, bool create_on_demand, size_t* idx, size_t* bitidx) {
|
|
// note: segment can be invalid or NULL.
|
|
mi_assert_internal(_mi_ptr_segment(segment + 1) == segment); // is it aligned on MI_SEGMENT_SIZE?
|
|
*idx = 0;
|
|
*bitidx = 0;
|
|
if ((uintptr_t)segment >= MI_SEGMENT_MAP_MAX_ADDRESS) return NULL;
|
|
const uintptr_t segindex = ((uintptr_t)segment) / MI_SEGMENT_MAP_PART_SPAN;
|
|
if (segindex >= MI_SEGMENT_MAP_MAX_PARTS) return NULL;
|
|
mi_segmap_part_t* part = mi_atomic_load_ptr_relaxed(mi_segmap_part_t, &mi_segment_map[segindex]);
|
|
|
|
// allocate on demand to reduce .bss footprint
|
|
if mi_unlikely(part == NULL) {
|
|
if (!create_on_demand) return NULL;
|
|
mi_memid_t memid;
|
|
part = (mi_segmap_part_t*)_mi_os_zalloc(sizeof(mi_segmap_part_t), &memid);
|
|
if (part == NULL) return NULL;
|
|
part->memid = memid;
|
|
mi_segmap_part_t* expected = NULL;
|
|
if (!mi_atomic_cas_ptr_strong_release(mi_segmap_part_t, &mi_segment_map[segindex], &expected, part)) {
|
|
_mi_os_free(part, sizeof(mi_segmap_part_t), memid);
|
|
part = expected;
|
|
if (part == NULL) return NULL;
|
|
}
|
|
}
|
|
mi_assert(part != NULL);
|
|
const uintptr_t offset = ((uintptr_t)segment) % MI_SEGMENT_MAP_PART_SPAN;
|
|
const uintptr_t bitofs = offset / MI_SEGMENT_MAP_PART_BIT_SPAN;
|
|
*idx = bitofs / MI_INTPTR_BITS;
|
|
*bitidx = bitofs % MI_INTPTR_BITS;
|
|
return part;
|
|
}
|
|
|
|
void _mi_segment_map_allocated_at(const mi_segment_t* segment) {
|
|
if (segment->memid.memkind == MI_MEM_ARENA) return; // we lookup segments first in the arena's and don't need the segment map
|
|
size_t index;
|
|
size_t bitidx;
|
|
mi_segmap_part_t* part = mi_segment_map_index_of(segment, true /* alloc map if needed */, &index, &bitidx);
|
|
if (part == NULL) return; // outside our address range..
|
|
uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);
|
|
uintptr_t newmask;
|
|
do {
|
|
newmask = (mask | ((uintptr_t)1 << bitidx));
|
|
} while (!mi_atomic_cas_weak_release(&part->map[index], &mask, newmask));
|
|
}
|
|
|
|
void _mi_segment_map_freed_at(const mi_segment_t* segment) {
|
|
if (segment->memid.memkind == MI_MEM_ARENA) return;
|
|
size_t index;
|
|
size_t bitidx;
|
|
mi_segmap_part_t* part = mi_segment_map_index_of(segment, false /* don't alloc if not present */, &index, &bitidx);
|
|
if (part == NULL) return; // outside our address range..
|
|
uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);
|
|
uintptr_t newmask;
|
|
do {
|
|
newmask = (mask & ~((uintptr_t)1 << bitidx));
|
|
} while (!mi_atomic_cas_weak_release(&part->map[index], &mask, newmask));
|
|
}
|
|
|
|
// Determine the segment belonging to a pointer or NULL if it is not in a valid segment.
|
|
static mi_segment_t* _mi_segment_of(const void* p) {
|
|
if (p == NULL) return NULL;
|
|
mi_segment_t* segment = _mi_ptr_segment(p); // segment can be NULL
|
|
size_t index;
|
|
size_t bitidx;
|
|
mi_segmap_part_t* part = mi_segment_map_index_of(segment, false /* dont alloc if not present */, &index, &bitidx);
|
|
if (part == NULL) return NULL;
|
|
const uintptr_t mask = mi_atomic_load_relaxed(&part->map[index]);
|
|
if mi_likely((mask & ((uintptr_t)1 << bitidx)) != 0) {
|
|
// yes, allocated by us
|
|
const bool cookie_ok = (_mi_ptr_cookie(segment) == segment->cookie);
|
|
if mi_likely(cookie_ok) {
|
|
return segment; // yes, allocated by us and valid
|
|
}
|
|
else {
|
|
_mi_error_message(EFAULT, "segment map found an invalid segment, possibly corrupted meta-data (address=%p, segment=%p)\n", p, segment);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Is this a valid pointer in our heap?
|
|
static bool mi_is_valid_pointer(const void* p) {
|
|
// first check if it is in an arena, then check if it is OS allocated
|
|
return (_mi_arena_contains(p) || _mi_segment_of(p) != NULL);
|
|
}
|
|
|
|
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {
|
|
return mi_is_valid_pointer(p);
|
|
}
|
|
|
|
void _mi_segment_map_unsafe_destroy(void) {
|
|
for (size_t i = 0; i < MI_SEGMENT_MAP_MAX_PARTS; i++) {
|
|
mi_segmap_part_t* part = mi_atomic_exchange_ptr_relaxed(mi_segmap_part_t, &mi_segment_map[i], NULL);
|
|
if (part != NULL) {
|
|
_mi_os_free(part, sizeof(mi_segmap_part_t), part->memid);
|
|
}
|
|
}
|
|
}
|