mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
gh-159111: Update vendored mimalloc to v2.5.2 (gh-159112)
* 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
This commit is contained in:
1 parent
ab5187ce84
commit
aff7a2e23b
45 files changed
+7624
-3500
No files matched your search
+1
-1
@@ -1034,7 +1034,7 @@ mimalloc
|
||||
|
||||
MIT License::
|
||||
|
||||
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
|
||||
Copyright (c) 2018-2025 Microsoft Corporation, Daan Leijen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1295,6 +1295,11 @@ Build changes
|
||||
|
||||
(Contributed by Stan Ulbrych, Florian Mayer and AnnaAr321 in :gh:`156049`.)
|
||||
|
||||
* Update the bundled `mimalloc library`_ from version 2.1.2 to 2.5.2.
|
||||
(Contributed by Donghee Na in :gh:`159111`.)
|
||||
|
||||
.. _mimalloc library: https://github.com/microsoft/mimalloc/
|
||||
|
||||
|
||||
C API changes
|
||||
=============
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2025, 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.
|
||||
-----------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
#ifndef MIMALLOC_STATS_H
|
||||
#define MIMALLOC_STATS_H
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define MI_STAT_VERSION 4 // increased on every backward incompatible change
|
||||
|
||||
// count allocation over time
|
||||
typedef struct mi_stat_count_s {
|
||||
int64_t total; // total allocated
|
||||
int64_t peak; // peak allocation
|
||||
int64_t current; // current allocation
|
||||
} mi_stat_count_t;
|
||||
|
||||
// counters only increase
|
||||
typedef struct mi_stat_counter_s {
|
||||
int64_t total; // total count
|
||||
} mi_stat_counter_t;
|
||||
|
||||
#define MI_STAT_FIELDS() \
|
||||
MI_STAT_COUNT(pages) /* count of mimalloc pages */ \
|
||||
MI_STAT_COUNT(reserved) /* reserved memory bytes */ \
|
||||
MI_STAT_COUNT(committed) /* committed bytes */ \
|
||||
MI_STAT_COUNTER(reset) /* reset bytes */ \
|
||||
MI_STAT_COUNTER(purged) /* purged bytes */ \
|
||||
MI_STAT_COUNT(page_committed) /* committed memory inside pages */ \
|
||||
MI_STAT_COUNT(pages_abandoned) /* abandoned pages count */ \
|
||||
MI_STAT_COUNT(threads) /* number of threads */ \
|
||||
MI_STAT_COUNT(malloc_normal) /* allocated bytes <= MI_LARGE_OBJ_SIZE_MAX */ \
|
||||
MI_STAT_COUNT(malloc_huge) /* allocated bytes in huge pages */ \
|
||||
MI_STAT_COUNT(malloc_requested) /* malloc requested bytes */ \
|
||||
\
|
||||
MI_STAT_COUNTER(mmap_calls) \
|
||||
MI_STAT_COUNTER(commit_calls) \
|
||||
MI_STAT_COUNTER(reset_calls) \
|
||||
MI_STAT_COUNTER(purge_calls) \
|
||||
MI_STAT_COUNTER(arena_count) /* number of memory arena's */ \
|
||||
MI_STAT_COUNTER(malloc_normal_count) /* number of blocks <= MI_LARGE_OBJ_SIZE_MAX */ \
|
||||
MI_STAT_COUNTER(malloc_huge_count) /* number of huge bloks */ \
|
||||
MI_STAT_COUNTER(malloc_guarded_count) /* number of allocations with guard pages */ \
|
||||
\
|
||||
/* internal statistics */ \
|
||||
MI_STAT_COUNTER(arena_rollback_count) \
|
||||
MI_STAT_COUNTER(arena_purges) \
|
||||
MI_STAT_COUNTER(pages_extended) /* number of page extensions */ \
|
||||
MI_STAT_COUNTER(pages_retire) /* number of pages that are retired */ \
|
||||
MI_STAT_COUNTER(page_searches) /* total pages searched for a fresh page */ \
|
||||
MI_STAT_COUNTER(page_searches_count) /* searched count for a fresh page */ \
|
||||
/* only on v1 and v2 */ \
|
||||
MI_STAT_COUNT(segments) \
|
||||
MI_STAT_COUNT(segments_abandoned) \
|
||||
MI_STAT_COUNT(segments_cache) \
|
||||
MI_STAT_COUNT(_segments_reserved) \
|
||||
/* only on v3 */ \
|
||||
MI_STAT_COUNTER(pages_reclaim_on_alloc) \
|
||||
MI_STAT_COUNTER(pages_reclaim_on_free) \
|
||||
MI_STAT_COUNTER(pages_reabandon_full) \
|
||||
MI_STAT_COUNTER(pages_unabandon_busy_wait) \
|
||||
|
||||
|
||||
// Define the statistics structure
|
||||
#define MI_BIN_HUGE (73U) // see types.h
|
||||
#define MI_STAT_COUNT(stat) mi_stat_count_t stat;
|
||||
#define MI_STAT_COUNTER(stat) mi_stat_counter_t stat;
|
||||
|
||||
typedef struct mi_stats_s
|
||||
{
|
||||
size_t size; // size of the mi_stats_t structure
|
||||
size_t version;
|
||||
|
||||
MI_STAT_FIELDS()
|
||||
|
||||
// future extension
|
||||
mi_stat_count_t _stat_reserved[4];
|
||||
mi_stat_counter_t _stat_counter_reserved[4];
|
||||
|
||||
// size segregated statistics
|
||||
mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]; // allocation per size bin
|
||||
mi_stat_count_t page_bins[MI_BIN_HUGE+1]; // pages allocated per size bin
|
||||
} mi_stats_t;
|
||||
|
||||
#undef MI_STAT_COUNT
|
||||
#undef MI_STAT_COUNTER
|
||||
|
||||
// helper
|
||||
#define mi_stats_t_decl(name) mi_stats_t name = { 0 }; name.size = sizeof(mi_stats_t); name.version = MI_STAT_VERSION;
|
||||
|
||||
// Exported definitions
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
mi_decl_export bool mi_stats_get( mi_stats_t* stats ) mi_attr_noexcept;
|
||||
mi_decl_export char* mi_stats_get_json( size_t buf_size, char* buf ) mi_attr_noexcept; // use mi_free to free the result if the input buf == NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MIMALLOC_STATS_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
|
||||
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.
|
||||
@@ -8,7 +8,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#ifndef MIMALLOC_H
|
||||
#define MIMALLOC_H
|
||||
|
||||
#define MI_MALLOC_VERSION 212 // major + 2 digits minor
|
||||
#define MI_MALLOC_VERSION 20502 // major + 2 digits minor + 2 digits patch
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Compiler specific attributes
|
||||
@@ -95,7 +95,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
// Includes
|
||||
// ------------------------------------------------------
|
||||
|
||||
#include <stddef.h> // size_t
|
||||
#include <stddef.h> // size_t, wchar_t
|
||||
#include <stdbool.h> // bool
|
||||
#include <stdint.h> // INTPTR_MAX
|
||||
|
||||
@@ -115,12 +115,12 @@ mi_decl_export void* mi_expand(void* p, size_t newsize)
|
||||
mi_decl_export void mi_free(void* p) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept;
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Extended functionality
|
||||
// ------------------------------------------------------
|
||||
#define MI_SMALL_WSIZE_MAX (128)
|
||||
#define MI_SMALL_WSIZE_MAX 128
|
||||
#define MI_SMALL_SIZE_MAX (MI_SMALL_WSIZE_MAX*sizeof(void*))
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
@@ -134,6 +134,8 @@ mi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize)
|
||||
mi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept;
|
||||
|
||||
// for compat with v3.
|
||||
mi_decl_export void mi_free_small(void* p) mi_attr_noexcept;
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Internals
|
||||
@@ -154,16 +156,21 @@ mi_decl_export void mi_stats_reset(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_stats_merge(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_stats_print(void* out) mi_attr_noexcept; // backward compatibility: `out` is ignored and should be NULL
|
||||
mi_decl_export void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export void mi_process_init(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_thread_init(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_thread_done(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept;
|
||||
mi_decl_export void mi_options_print(void) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs,
|
||||
size_t* current_rss, size_t* peak_rss,
|
||||
size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept;
|
||||
|
||||
|
||||
// Generally do not use the following as these are usually called automatically
|
||||
mi_decl_export void mi_process_init(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_cdecl mi_process_done(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_thread_init(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_thread_done(void) mi_attr_noexcept;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Aligned allocation
|
||||
// Note that `alignment` always follows `size` for consistency with unaligned
|
||||
@@ -180,6 +187,22 @@ mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned(void* p, size_t newsiz
|
||||
mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Return allocated block size (if the return value is not NULL)
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2);
|
||||
mi_decl_nodiscard mi_decl_export void* mi_urealloc(void* p, size_t newsize, size_t* block_size_pre, size_t* block_size_post) mi_attr_noexcept mi_attr_alloc_size(2);
|
||||
mi_decl_export void mi_ufree(void* p, size_t* block_size) mi_attr_noexcept;
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2);
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Heaps: first-class, but can only allocate from the same thread that created it.
|
||||
// -------------------------------------------------------------------------------------
|
||||
@@ -200,6 +223,7 @@ mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc(mi_heap_t
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
|
||||
|
||||
mi_decl_nodiscard mi_decl_export void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3);
|
||||
mi_decl_nodiscard mi_decl_export void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3,4);
|
||||
@@ -207,7 +231,7 @@ mi_decl_nodiscard mi_decl_export void* mi_heap_reallocf(mi_heap_t* heap, void* p
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept;
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
|
||||
@@ -259,23 +283,25 @@ typedef struct mi_heap_area_s {
|
||||
size_t used; // number of allocated blocks
|
||||
size_t block_size; // size in bytes of each block
|
||||
size_t full_block_size; // size in bytes of a full block including padding and metadata.
|
||||
int heap_tag; // heap tag associated with this area
|
||||
} mi_heap_area_t;
|
||||
|
||||
typedef bool (mi_cdecl mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg);
|
||||
|
||||
mi_decl_export bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_all_blocks, mi_block_visit_fun* visitor, void* arg);
|
||||
mi_decl_export bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);
|
||||
|
||||
// Experimental
|
||||
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export bool mi_is_redirected(void) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept;
|
||||
mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept;
|
||||
mi_decl_export int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept;
|
||||
mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept;
|
||||
mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept;
|
||||
mi_decl_export int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept;
|
||||
mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept;
|
||||
mi_decl_export void mi_arenas_print(void) mi_attr_noexcept;
|
||||
|
||||
// Experimental: heaps associated with specific memory arena's
|
||||
typedef int mi_arena_id_t;
|
||||
@@ -289,8 +315,39 @@ mi_decl_export bool mi_manage_os_memory_ex(void* start, size_t size, bool is_co
|
||||
mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id);
|
||||
#endif
|
||||
|
||||
|
||||
// Experimental: allow sub-processes whose memory areas stay separated (and no reclamation between them)
|
||||
// Used for example for separate interpreters in one process.
|
||||
typedef void* mi_subproc_id_t;
|
||||
mi_decl_export mi_subproc_id_t mi_subproc_main(void);
|
||||
mi_decl_export mi_subproc_id_t mi_subproc_new(void);
|
||||
mi_decl_export void mi_subproc_delete(mi_subproc_id_t subproc);
|
||||
mi_decl_export void mi_subproc_add_current_thread(mi_subproc_id_t subproc); // this should be called right after a thread is created (and no allocation has taken place yet)
|
||||
|
||||
// Experimental: visit abandoned heap areas (that are not owned by a specific heap)
|
||||
mi_decl_export bool mi_abandoned_visit_blocks(mi_subproc_id_t subproc_id, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg);
|
||||
|
||||
// Experimental: objects followed by a guard page.
|
||||
// A sample rate of 0 disables guarded objects, while 1 uses a guard page for every object.
|
||||
// A seed of 0 uses a random start point. Only objects within the size bound are eligable for guard pages.
|
||||
mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed);
|
||||
mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max);
|
||||
|
||||
// Experimental: communicate that the thread is part of a threadpool
|
||||
mi_decl_export void mi_thread_set_in_threadpool(void) mi_attr_noexcept;
|
||||
|
||||
// Experimental: create a new heap with a specified heap tag. Set `allow_destroy` to false to allow the thread
|
||||
// to reclaim abandoned memory (with a compatible heap_tag and arena_id) but in that case `mi_heap_destroy` will
|
||||
// fall back to `mi_heap_delete`.
|
||||
mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id);
|
||||
|
||||
// Experimental and unsafe: assumes the page of `p` is only accessed by the calling thread
|
||||
mi_decl_nodiscard mi_decl_export bool mi_unsafe_heap_page_is_under_utilized(mi_heap_t* heap, void* p, size_t perc_threshold) mi_attr_noexcept;
|
||||
|
||||
// deprecated
|
||||
mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept;
|
||||
mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept;
|
||||
mi_decl_export void mi_collect_reduce(size_t target_thread_owned) mi_attr_noexcept;
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -312,46 +369,76 @@ mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size
|
||||
#define mi_heap_recalloc_tp(hp,p,tp,n) ((tp*)mi_heap_recalloc(hp,p,n,sizeof(tp)))
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Compatibility with v3
|
||||
// ------------------------------------------------------
|
||||
|
||||
typedef mi_heap_t mi_theap_t;
|
||||
|
||||
#define mi_theap_get_default() mi_heap_get_default()
|
||||
#define mi_theap_set_default(hp) mi_heap_set_default(hp)
|
||||
#define mi_theap_collect(hp,force) mi_heap_collect(hp,force)
|
||||
#define mi_theap_malloc(hp,sz) mi_heap_malloc(hp,sz)
|
||||
#define mi_theap_zalloc(hp,sz) mi_heap_zalloc(hp,sz)
|
||||
#define mi_theap_calloc(hp,cnt,sz) mi_heap_calloc(hp,cnt,sz)
|
||||
#define mi_theap_malloc_small(hp,sz) mi_heap_malloc_small(hp,sz)
|
||||
#define mi_theap_malloc_aligned(hp,sz,a) mi_heap_malloc_aligned(hp,sz,a)
|
||||
#define mi_theap_realloc(hp,p,newsz) mi_heap_realloc(hp,p,newsz)
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Options
|
||||
// ------------------------------------------------------
|
||||
|
||||
typedef enum mi_option_e {
|
||||
// stable options
|
||||
mi_option_show_errors, // print error messages
|
||||
mi_option_show_stats, // print statistics on termination
|
||||
mi_option_verbose, // print verbose messages
|
||||
// the following options are experimental (see src/options.h)
|
||||
mi_option_eager_commit, // eager commit segments? (after `eager_commit_delay` segments) (=1)
|
||||
mi_option_arena_eager_commit, // eager commit arenas? Use 2 to enable just on overcommit systems (=2)
|
||||
mi_option_purge_decommits, // should a memory purge decommit (or only reset) (=1)
|
||||
mi_option_allow_large_os_pages, // allow large (2MiB) OS pages, implies eager commit
|
||||
mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB/page) at startup
|
||||
mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node
|
||||
mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup
|
||||
mi_option_show_errors, // print error messages
|
||||
mi_option_show_stats, // print statistics on termination
|
||||
mi_option_verbose, // print verbose messages
|
||||
// advanced options
|
||||
mi_option_eager_commit, // eager commit segments? (after `eager_commit_delay` segments) (=1)
|
||||
mi_option_arena_eager_commit, // eager commit arenas? Use 2 to enable just on overcommit systems (=2)
|
||||
mi_option_purge_decommits, // should a memory purge decommit? (=1). Set to 0 to use memory reset on a purge (instead of decommit)
|
||||
mi_option_allow_large_os_pages, // allow use of large (2 or 4 MiB) OS pages, implies eager commit.
|
||||
mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB pages) at startup
|
||||
mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node
|
||||
mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup (internally, this value is in KiB; use `mi_option_get_size`)
|
||||
mi_option_deprecated_segment_cache,
|
||||
mi_option_deprecated_page_reset,
|
||||
mi_option_abandoned_page_purge, // immediately purge delayed purges on thread termination
|
||||
mi_option_abandoned_page_purge, // immediately purge delayed purges on thread termination
|
||||
mi_option_deprecated_segment_reset,
|
||||
mi_option_eager_commit_delay,
|
||||
mi_option_purge_delay, // memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all.
|
||||
mi_option_use_numa_nodes, // 0 = use all available numa nodes, otherwise use at most N nodes.
|
||||
mi_option_limit_os_alloc, // 1 = do not use OS memory for allocation (but only programmatically reserved arenas)
|
||||
mi_option_os_tag, // tag used for OS logging (macOS only for now)
|
||||
mi_option_max_errors, // issue at most N error messages
|
||||
mi_option_max_warnings, // issue at most N warning messages
|
||||
mi_option_max_segment_reclaim,
|
||||
mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe.
|
||||
mi_option_arena_reserve, // initial memory size in KiB for arena reservation (1GiB on 64-bit)
|
||||
mi_option_arena_purge_mult,
|
||||
mi_option_eager_commit_delay, // the first N segments per thread are not eagerly committed (but per page in the segment on demand)
|
||||
mi_option_purge_delay, // memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all. (=10)
|
||||
mi_option_use_numa_nodes, // 0 = use all available numa nodes, otherwise use at most N nodes.
|
||||
mi_option_disallow_os_alloc, // 1 = do not use OS memory for allocation (but only programmatically reserved arenas)
|
||||
mi_option_os_tag, // tag used for OS logging (macOS only for now) (=100)
|
||||
mi_option_max_errors, // issue at most N error messages
|
||||
mi_option_max_warnings, // issue at most N warning messages
|
||||
mi_option_max_segment_reclaim, // max. percentage of the abandoned segments can be reclaimed per try (=10%)
|
||||
mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe
|
||||
mi_option_arena_reserve, // initial memory size for arena reservation (= 1 GiB on 64-bit) (internally, this value is in KiB; use `mi_option_get_size`)
|
||||
mi_option_arena_purge_mult, // multiplier for `purge_delay` for the purging delay for arenas (=10)
|
||||
mi_option_purge_extend_delay,
|
||||
mi_option_abandoned_reclaim_on_free, // allow to reclaim an abandoned segment on a free (=1)
|
||||
mi_option_disallow_arena_alloc, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
||||
mi_option_retry_on_oom, // retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows)
|
||||
mi_option_visit_abandoned, // allow visiting heap blocks from abandoned threads (=0)
|
||||
mi_option_guarded_min, // only used when building with MI_GUARDED: minimal rounded object size for guarded objects (=0)
|
||||
mi_option_guarded_max, // only used when building with MI_GUARDED: maximal rounded object size for guarded objects (=0)
|
||||
mi_option_guarded_precise, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
|
||||
mi_option_guarded_sample_rate, // 1 out of N allocations in the min/max range will be guarded (=1000)
|
||||
mi_option_guarded_sample_seed, // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0)
|
||||
mi_option_target_segments_per_thread, // experimental (=0)
|
||||
mi_option_generic_collect, // collect heaps every N (=10000) generic allocation calls
|
||||
mi_option_allow_thp, // allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process.
|
||||
_mi_option_last,
|
||||
// legacy option names
|
||||
mi_option_large_os_pages = mi_option_allow_large_os_pages,
|
||||
mi_option_eager_region_commit = mi_option_arena_eager_commit,
|
||||
mi_option_reset_decommits = mi_option_purge_decommits,
|
||||
mi_option_reset_delay = mi_option_purge_delay,
|
||||
mi_option_abandoned_page_reset = mi_option_abandoned_page_purge
|
||||
mi_option_abandoned_page_reset = mi_option_abandoned_page_purge,
|
||||
mi_option_limit_os_alloc = mi_option_disallow_os_alloc
|
||||
} mi_option_t;
|
||||
|
||||
|
||||
@@ -380,25 +467,26 @@ mi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p) mi_
|
||||
mi_decl_nodiscard mi_decl_export size_t mi_malloc_good_size(size_t size) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept;
|
||||
mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size); // mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1);
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1);
|
||||
|
||||
mi_decl_nodiscard mi_decl_export void* mi_reallocarray(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3);
|
||||
mi_decl_nodiscard mi_decl_export int mi_reallocarr(void* p, size_t count, size_t size) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export int mi_reallocarr(void* ptrp, size_t count, size_t size) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept;
|
||||
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept;
|
||||
mi_decl_export int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept;
|
||||
|
||||
mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept;
|
||||
mi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept;
|
||||
mi_decl_export void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept;
|
||||
mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept;
|
||||
|
||||
// wide characters
|
||||
mi_decl_export int mi_wdupenv_s(wchar_t** buf, size_t* size, const wchar_t* name) mi_attr_noexcept;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict wchar_t* mi_wcsdup(const wchar_t* s) mi_attr_noexcept mi_attr_malloc;
|
||||
mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc;
|
||||
|
||||
// The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`.
|
||||
// (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception).
|
||||
@@ -494,7 +582,7 @@ template<class T, bool _mi_destroy> struct _mi_heap_stl_allocator_common : publi
|
||||
using typename _mi_stl_allocator_common<T>::value_type;
|
||||
using typename _mi_stl_allocator_common<T>::pointer;
|
||||
|
||||
_mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp) { } /* will not delete nor destroy the passed in heap */
|
||||
_mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp, [](mi_heap_t*) {}) {} /* will not delete nor destroy the passed in heap */
|
||||
|
||||
#if (__cplusplus >= 201703L) // C++17
|
||||
mi_decl_nodiscard T* allocate(size_type count) { return static_cast<T*>(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023 Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2024 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.
|
||||
@@ -8,6 +8,17 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#ifndef MIMALLOC_ATOMIC_H
|
||||
#define MIMALLOC_ATOMIC_H
|
||||
|
||||
// include windows.h or pthreads.h
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif !defined(__wasi__) && (!defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__))
|
||||
#define MI_USE_PTHREADS
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Atomics
|
||||
// We need to be portable between C, C++, and MSVC.
|
||||
@@ -20,33 +31,33 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#if defined(__cplusplus)
|
||||
// Use C++ atomics
|
||||
#include <atomic>
|
||||
#define _Atomic(tp) std::atomic<tp>
|
||||
#define mi_atomic(name) std::atomic_##name
|
||||
#define mi_memory_order(name) std::memory_order_##name
|
||||
#if (__cplusplus >= 202002L) // c++20, see issue #571
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define _Atomic(tp) std::atomic<tp>
|
||||
#define mi_atomic(name) std::atomic_##name
|
||||
#define mi_memory_order(name) std::memory_order_##name
|
||||
#if (__cplusplus >= 202002L) // c++20, see issue #571
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#elif !defined(ATOMIC_VAR_INIT)
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#else
|
||||
#define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x)
|
||||
#define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x)
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
// Use MSVC C wrapper for C11 atomics
|
||||
#define _Atomic(tp) tp
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define mi_atomic(name) mi_atomic_##name
|
||||
#define mi_memory_order(name) mi_memory_order_##name
|
||||
#define _Atomic(tp) tp
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define mi_atomic(name) mi_atomic_##name
|
||||
#define mi_memory_order(name) mi_memory_order_##name
|
||||
#else
|
||||
// Use C11 atomics
|
||||
#include <stdatomic.h>
|
||||
#define mi_atomic(name) atomic_##name
|
||||
#define mi_memory_order(name) memory_order_##name
|
||||
#define mi_atomic(name) atomic_##name
|
||||
#define mi_memory_order(name) memory_order_##name
|
||||
#if (__STDC_VERSION__ >= 201710L) // c17, see issue #735
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#elif !defined(ATOMIC_VAR_INIT)
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#define MI_ATOMIC_VAR_INIT(x) x
|
||||
#else
|
||||
#define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x)
|
||||
#define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -61,6 +72,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#define mi_atomic_load_relaxed(p) mi_atomic(load_explicit)(p,mi_memory_order(relaxed))
|
||||
#define mi_atomic_store_release(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(release))
|
||||
#define mi_atomic_store_relaxed(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed))
|
||||
#define mi_atomic_exchange_relaxed(p,x) mi_atomic(exchange_explicit)(p,x,mi_memory_order(relaxed))
|
||||
#define mi_atomic_exchange_release(p,x) mi_atomic(exchange_explicit)(p,x,mi_memory_order(release))
|
||||
#define mi_atomic_exchange_acq_rel(p,x) mi_atomic(exchange_explicit)(p,x,mi_memory_order(acq_rel))
|
||||
#define mi_atomic_cas_weak_release(p,exp,des) mi_atomic_cas_weak(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed))
|
||||
@@ -99,6 +111,8 @@ static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub);
|
||||
#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release(p,exp,(tp*)des)
|
||||
#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel(p,exp,(tp*)des)
|
||||
#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release(p,exp,(tp*)des)
|
||||
#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des) mi_atomic_cas_strong_acq_rel(p,exp,(tp*)des)
|
||||
#define mi_atomic_exchange_ptr_relaxed(tp,p,x) mi_atomic_exchange_relaxed(p,(tp*)x)
|
||||
#define mi_atomic_exchange_ptr_release(tp,p,x) mi_atomic_exchange_release(p,(tp*)x)
|
||||
#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) mi_atomic_exchange_acq_rel(p,(tp*)x)
|
||||
#else
|
||||
@@ -107,6 +121,8 @@ static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub);
|
||||
#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release(p,exp,des)
|
||||
#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel(p,exp,des)
|
||||
#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release(p,exp,des)
|
||||
#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des) mi_atomic_cas_strong_acq_rel(p,exp,des)
|
||||
#define mi_atomic_exchange_ptr_relaxed(tp,p,x) mi_atomic_exchange_relaxed(p,x)
|
||||
#define mi_atomic_exchange_ptr_release(tp,p,x) mi_atomic_exchange_release(p,x)
|
||||
#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) mi_atomic_exchange_acq_rel(p,x)
|
||||
#endif
|
||||
@@ -115,6 +131,12 @@ static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub);
|
||||
static inline int64_t mi_atomic_addi64_relaxed(volatile int64_t* p, int64_t add) {
|
||||
return mi_atomic(fetch_add_explicit)((_Atomic(int64_t)*)p, add, mi_memory_order(relaxed));
|
||||
}
|
||||
static inline void mi_atomic_void_addi64_relaxed(volatile int64_t* p, const volatile int64_t* padd) {
|
||||
const int64_t add = mi_atomic_load_relaxed((_Atomic(int64_t)*)padd);
|
||||
if (add != 0) {
|
||||
mi_atomic(fetch_add_explicit)((_Atomic(int64_t)*)p, add, mi_memory_order(relaxed));
|
||||
}
|
||||
}
|
||||
static inline void mi_atomic_maxi64_relaxed(volatile int64_t* p, int64_t x) {
|
||||
int64_t current = mi_atomic_load_relaxed((_Atomic(int64_t)*)p);
|
||||
while (current < x && !mi_atomic_cas_weak_release((_Atomic(int64_t)*)p, ¤t, x)) { /* nothing */ };
|
||||
@@ -132,16 +154,18 @@ static inline void mi_atomic_maxi64_relaxed(volatile int64_t* p, int64_t x) {
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
// MSVC C compilation wrapper that uses Interlocked operations to model C11 atomics.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
// Deprecated: MSVC plain C compilation wrapper that uses Interlocked operations to model C11 atomics.
|
||||
// It is recommended to always compile as C++ when using MSVC.
|
||||
|
||||
#include <intrin.h>
|
||||
#ifdef _WIN64
|
||||
typedef LONG64 msc_intptr_t;
|
||||
#define MI_64(f) f##64
|
||||
typedef LONG64 msc_intptr_t;
|
||||
#define MI_MSC_64(f) f##64
|
||||
#define MI_MSC_XX(f) f##64
|
||||
#else
|
||||
typedef LONG msc_intptr_t;
|
||||
#define MI_64(f) f
|
||||
typedef LONG msc_intptr_t;
|
||||
#define MI_MSC_64(f) f
|
||||
#define MI_MSC_XX(f) f##32
|
||||
#endif
|
||||
|
||||
typedef enum mi_memory_order_e {
|
||||
@@ -155,23 +179,23 @@ typedef enum mi_memory_order_e {
|
||||
|
||||
static inline uintptr_t mi_atomic_fetch_add_explicit(_Atomic(uintptr_t)*p, uintptr_t add, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, (msc_intptr_t)add);
|
||||
return (uintptr_t)MI_MSC_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, (msc_intptr_t)add);
|
||||
}
|
||||
static inline uintptr_t mi_atomic_fetch_sub_explicit(_Atomic(uintptr_t)*p, uintptr_t sub, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, -((msc_intptr_t)sub));
|
||||
return (uintptr_t)MI_MSC_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, -((msc_intptr_t)sub));
|
||||
}
|
||||
static inline uintptr_t mi_atomic_fetch_and_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
return (uintptr_t)MI_64(_InterlockedAnd)((volatile msc_intptr_t*)p, (msc_intptr_t)x);
|
||||
return (uintptr_t)MI_MSC_64(_InterlockedAnd)((volatile msc_intptr_t*)p, (msc_intptr_t)x);
|
||||
}
|
||||
static inline uintptr_t mi_atomic_fetch_or_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
return (uintptr_t)MI_64(_InterlockedOr)((volatile msc_intptr_t*)p, (msc_intptr_t)x);
|
||||
return (uintptr_t)MI_MSC_64(_InterlockedOr)((volatile msc_intptr_t*)p, (msc_intptr_t)x);
|
||||
}
|
||||
static inline bool mi_atomic_compare_exchange_strong_explicit(_Atomic(uintptr_t)*p, uintptr_t* expected, uintptr_t desired, mi_memory_order mo1, mi_memory_order mo2) {
|
||||
(void)(mo1); (void)(mo2);
|
||||
uintptr_t read = (uintptr_t)MI_64(_InterlockedCompareExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)desired, (msc_intptr_t)(*expected));
|
||||
const uintptr_t read = (uintptr_t)MI_MSC_64(_InterlockedCompareExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)desired, (msc_intptr_t)(*expected));
|
||||
if (read == *expected) {
|
||||
return true;
|
||||
}
|
||||
@@ -185,82 +209,139 @@ static inline bool mi_atomic_compare_exchange_weak_explicit(_Atomic(uintptr_t)*p
|
||||
}
|
||||
static inline uintptr_t mi_atomic_exchange_explicit(_Atomic(uintptr_t)*p, uintptr_t exchange, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
return (uintptr_t)MI_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange);
|
||||
}
|
||||
static inline void mi_atomic_thread_fence(mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
_Atomic(uintptr_t) x = 0;
|
||||
mi_atomic_exchange_explicit(&x, 1, mo);
|
||||
return (uintptr_t)MI_MSC_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange);
|
||||
}
|
||||
|
||||
static inline uintptr_t mi_atomic_load_explicit(_Atomic(uintptr_t) const* p, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
return *p;
|
||||
#else
|
||||
uintptr_t x = *p;
|
||||
if (mo > mi_memory_order_relaxed) {
|
||||
while (!mi_atomic_compare_exchange_weak_explicit((_Atomic(uintptr_t)*)p, &x, x, mo, mi_memory_order_relaxed)) { /* nothing */ };
|
||||
}
|
||||
return x;
|
||||
#endif
|
||||
// assert(mo<=mi_memory_order_acquire); // others are not used by mimalloc
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
return (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
|
||||
#elif defined(_M_ARM) || defined(_M_ARM64)
|
||||
if (mo == mi_memory_order_relaxed) {
|
||||
return (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
|
||||
}
|
||||
#if !defined(__clang__) // work around __ldar missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
|
||||
else if (mo <= mi_memory_order_acquire) {
|
||||
return MI_MSC_XX(__ldar)((volatile const uintptr_t*)p);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
const uintptr_t u = (uintptr_t)MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
|
||||
__dmb(15); // _ARM(64)_BARRIER_SY
|
||||
return u;
|
||||
}
|
||||
#else
|
||||
#warning "define mi_atomic_load_explicit for MSVC C compilation on this platform (which should be readonly, see issue #1277)"
|
||||
return MI_MSC_XX(__iso_volatile_load)((volatile const intptr_t*)p);
|
||||
#endif
|
||||
}
|
||||
static inline void mi_atomic_store_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
*p = x;
|
||||
#else
|
||||
mi_atomic_exchange_explicit(p, x, mo);
|
||||
#endif
|
||||
// assert(mo<=mi_memory_order_release); // others are not used by mimalloc
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
MI_MSC_XX(__iso_volatile_store)((volatile intptr_t*)p, x);
|
||||
#elif defined(_M_ARM) || defined(_M_ARM64)
|
||||
if (mo == mi_memory_order_relaxed) {
|
||||
MI_MSC_XX(__iso_volatile_store)((volatile intptr_t*)p, x);
|
||||
}
|
||||
#if !defined(__clang__) // work around __stlr missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
|
||||
else if (mo <= mi_memory_order_release) {
|
||||
MI_MSC_XX(__stlr)((volatile uintptr_t*)p,x);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
mi_atomic_exchange_explicit(p, x, mo);
|
||||
}
|
||||
#else
|
||||
mi_atomic_exchange_explicit(p, x, mo);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int64_t mi_atomic_loadi64_explicit(_Atomic(int64_t)*p, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
#if defined(_M_X64)
|
||||
return *p;
|
||||
#else
|
||||
int64_t old = *p;
|
||||
int64_t x = old;
|
||||
while ((old = InterlockedCompareExchange64(p, x, old)) != x) {
|
||||
x = old;
|
||||
}
|
||||
return x;
|
||||
#endif
|
||||
// assert(mo<=mi_memory_order_acquire); // others are not used by mimalloc
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
return __iso_volatile_load64((volatile const int64_t*)p);
|
||||
#elif defined(_M_ARM) || defined(_M_ARM64)
|
||||
if (mo == mi_memory_order_relaxed) {
|
||||
return __iso_volatile_load64((volatile const int64_t*)p);
|
||||
}
|
||||
#if defined(_M_ARM64) && !defined(__clang__) // work around __ldar64 missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
|
||||
else if (mo <= mi_memory_order_acquire) {
|
||||
return __ldar64((volatile const uintptr_t*)p);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
const int64_t i = __iso_volatile_load64((volatile const int64_t*)p);
|
||||
__dmb(15); // _ARM(64)_BARRIER_SY
|
||||
return i;
|
||||
}
|
||||
#else
|
||||
#warning "define mi_atomic_loadi64_explicit for MSVC C compilation on this platform (which should be readonly, see issue #1277)"
|
||||
return __iso_volatile_load64((volatile const int64_t*)p);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void mi_atomic_storei64_explicit(_Atomic(int64_t)*p, int64_t x, mi_memory_order mo) {
|
||||
(void)(mo);
|
||||
#if defined(x_M_IX86) || defined(_M_X64)
|
||||
*p = x;
|
||||
#else
|
||||
InterlockedExchange64(p, x);
|
||||
#endif
|
||||
// assert(mo<=mi_memory_order_release); // others are not used by mimalloc
|
||||
#if defined(_M_IX86) || defined(_M_X64)
|
||||
__iso_volatile_store64((volatile int64_t*)p,x);
|
||||
#elif defined(_M_ARM) || defined(_M_ARM64)
|
||||
if (mo == mi_memory_order_relaxed) {
|
||||
__iso_volatile_store64((volatile int64_t*)p,x);
|
||||
}
|
||||
#if defined(_M_ARM64) && !defined(__clang__) // work around __stlr64 missing in clang-cl, see https://github.com/llvm/llvm-project/issues/121689
|
||||
else if (mo == mi_memory_order_release) {
|
||||
__stlr64((volatile uint64_t*)p, (uint64_t)x);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
InterlockedExchange64(p, x);
|
||||
}
|
||||
#else
|
||||
InterlockedExchange64(p, x);
|
||||
#endif
|
||||
}
|
||||
|
||||
// These are used by the statistics
|
||||
static inline int64_t mi_atomic_addi64_relaxed(volatile _Atomic(int64_t)*p, int64_t add) {
|
||||
#ifdef _WIN64
|
||||
return (int64_t)mi_atomic_addi((int64_t*)p, add);
|
||||
#else
|
||||
int64_t current;
|
||||
int64_t sum;
|
||||
do {
|
||||
current = *p;
|
||||
sum = current + add;
|
||||
} while (_InterlockedCompareExchange64(p, sum, current) != current);
|
||||
return current;
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
return (int64_t)mi_atomic_addi((int64_t*)p, add);
|
||||
#elif defined(_M_ARM)
|
||||
return _InterlockedExchangeAdd64(p, add);
|
||||
#else
|
||||
// x86
|
||||
int64_t current;
|
||||
int64_t sum;
|
||||
do {
|
||||
current = __iso_volatile_load64((volatile const int64_t*)p);
|
||||
sum = current + add;
|
||||
} while (_InterlockedCompareExchange64(p, sum, current) != current);
|
||||
return current;
|
||||
#endif
|
||||
}
|
||||
static inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)*p, int64_t x) {
|
||||
static inline void mi_atomic_void_addi64_relaxed(volatile int64_t* p, const volatile int64_t* padd) {
|
||||
const int64_t add = *padd;
|
||||
if (add != 0) {
|
||||
mi_atomic_addi64_relaxed((volatile _Atomic(int64_t)*)p, add);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)* p, int64_t x) {
|
||||
int64_t current;
|
||||
do {
|
||||
current = *p;
|
||||
} while (current < x && _InterlockedCompareExchange64(p, x, current) != current);
|
||||
}
|
||||
|
||||
static inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t*)p, int64_t i) {
|
||||
static inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t)* p, int64_t i) {
|
||||
mi_atomic_addi64_relaxed(p, i);
|
||||
}
|
||||
|
||||
static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p, int64_t* exp, int64_t des) {
|
||||
int64_t read = _InterlockedCompareExchange64(p, des, *exp);
|
||||
static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t)* p, int64_t* exp, int64_t des) {
|
||||
const int64_t read = _InterlockedCompareExchange64(p, des, *exp);
|
||||
if (read == *exp) {
|
||||
return true;
|
||||
}
|
||||
@@ -278,6 +359,8 @@ static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p,
|
||||
#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
|
||||
#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
|
||||
#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
|
||||
#define mi_atomic_cas_ptr_strong_acq_rel(tp,p,exp,des) mi_atomic_cas_strong_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des)
|
||||
#define mi_atomic_exchange_ptr_relaxed(tp,p,x) (tp*)mi_atomic_exchange_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
|
||||
#define mi_atomic_exchange_ptr_release(tp,p,x) (tp*)mi_atomic_exchange_release((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
|
||||
#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) (tp*)mi_atomic_exchange_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t)x)
|
||||
|
||||
@@ -300,18 +383,14 @@ static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub) {
|
||||
return (intptr_t)mi_atomic_addi(p, -sub);
|
||||
}
|
||||
|
||||
typedef _Atomic(uintptr_t) mi_atomic_once_t;
|
||||
|
||||
// Returns true only on the first invocation
|
||||
static inline bool mi_atomic_once( mi_atomic_once_t* once ) {
|
||||
if (mi_atomic_load_relaxed(once) != 0) return false; // quick test
|
||||
uintptr_t expected = 0;
|
||||
return mi_atomic_cas_strong_acq_rel(once, &expected, (uintptr_t)1); // try to set to 1
|
||||
}
|
||||
// ----------------------------------------------------------------------
|
||||
// Once and Guard
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
typedef _Atomic(uintptr_t) mi_atomic_guard_t;
|
||||
|
||||
// Allows only one thread to execute at a time
|
||||
// Allows only one thread to execute at a time (without blocking anyone)
|
||||
#define mi_atomic_guard(guard) \
|
||||
uintptr_t _mi_guard_expected = 0; \
|
||||
for(bool _mi_guard_once = true; \
|
||||
@@ -320,17 +399,13 @@ typedef _Atomic(uintptr_t) mi_atomic_guard_t;
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Yield
|
||||
#if defined(__cplusplus)
|
||||
#include <thread>
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
#if defined(_WIN32)
|
||||
static inline void mi_atomic_yield(void) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
static inline void mi_atomic_yield(void) {
|
||||
YieldProcessor();
|
||||
YieldProcessor(); // see issue #1215 and #1225 why this is preferred over __yield or SwitchToThread
|
||||
}
|
||||
#elif defined(__SSE2__)
|
||||
#include <emmintrin.h>
|
||||
@@ -340,14 +415,15 @@ static inline void mi_atomic_yield(void) {
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && \
|
||||
(defined(__x86_64__) || defined(__i386__) || \
|
||||
defined(__aarch64__) || defined(__arm__) || \
|
||||
defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__))
|
||||
defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__) || \
|
||||
defined(__riscv))
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
static inline void mi_atomic_yield(void) {
|
||||
__asm__ volatile ("pause" ::: "memory");
|
||||
}
|
||||
#elif defined(__aarch64__)
|
||||
static inline void mi_atomic_yield(void) {
|
||||
__asm__ volatile("wfe");
|
||||
__asm__ volatile("isb");
|
||||
}
|
||||
#elif defined(__arm__)
|
||||
#if __ARM_ARCH >= 7
|
||||
@@ -369,9 +445,18 @@ static inline void mi_atomic_yield(void) {
|
||||
__asm__ __volatile__ ("or 27,27,27" ::: "memory");
|
||||
}
|
||||
#endif
|
||||
#elif defined(__riscv)
|
||||
#if defined(__riscv_zihintpause)
|
||||
static inline void mi_atomic_yield(void) {
|
||||
__asm__ volatile("pause" ::: "memory");
|
||||
}
|
||||
#else
|
||||
static inline void mi_atomic_yield(void) {
|
||||
__asm__ volatile("nop" ::: "memory");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(__sun)
|
||||
// Fallback for other archs
|
||||
#include <synch.h>
|
||||
static inline void mi_atomic_yield(void) {
|
||||
smt_pause();
|
||||
@@ -381,6 +466,12 @@ static inline void mi_atomic_yield(void) {
|
||||
static inline void mi_atomic_yield(void) {
|
||||
sched_yield();
|
||||
}
|
||||
// Fallback for other archs
|
||||
#elif defined(__cplusplus)
|
||||
#include <thread>
|
||||
static inline void mi_atomic_yield(void) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
static inline void mi_atomic_yield(void) {
|
||||
@@ -388,5 +479,186 @@ static inline void mi_atomic_yield(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
static inline void mi_sleep0(void) {
|
||||
Sleep(0);
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
static inline void mi_sleep0(void) {
|
||||
sleep(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void mi_atomic_yield_sleep( size_t* ticks, const size_t ticks_until_sleep ) {
|
||||
const size_t n = *ticks;
|
||||
if (n==0 || n > ticks_until_sleep) {
|
||||
*ticks = ticks_until_sleep; // reset
|
||||
}
|
||||
else {
|
||||
*ticks = n-1;
|
||||
}
|
||||
if (n>1) {
|
||||
mi_atomic_yield();
|
||||
}
|
||||
else {
|
||||
mi_sleep0();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Locks
|
||||
// These should be light-weight in-process only locks.
|
||||
// Only used for reserving arena's and to maintain the abandoned list.
|
||||
// ----------------------------------------------------------------------
|
||||
#if _MSC_VER
|
||||
#pragma warning(disable:26110) // unlock with holding lock
|
||||
#endif
|
||||
|
||||
#define mi_lock(lock) for(bool _mi_go = (mi_lock_acquire(lock),true); _mi_go; (mi_lock_release(lock), _mi_go=false) )
|
||||
#define mi_lock_maybe(lock,acquire) for(bool _mi_go = (acquire ? (mi_lock_acquire(lock),true) : true); _mi_go; _mi_go = (acquire ? (mi_lock_release(lock),false) : false) )
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
typedef struct mi_lock_s {
|
||||
SRWLOCK mutex; // slim reader-writer lock
|
||||
} mi_lock_t;
|
||||
|
||||
#define MI_LOCK_INITIALIZER { SRWLOCK_INIT }
|
||||
|
||||
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
|
||||
return TryAcquireSRWLockExclusive(&lock->mutex);
|
||||
}
|
||||
static inline void mi_lock_acquire(mi_lock_t* lock) {
|
||||
AcquireSRWLockExclusive(&lock->mutex);
|
||||
}
|
||||
static inline void mi_lock_release(mi_lock_t* lock) {
|
||||
ReleaseSRWLockExclusive(&lock->mutex);
|
||||
}
|
||||
static inline void mi_lock_init(mi_lock_t* lock) {
|
||||
InitializeSRWLock(&lock->mutex);
|
||||
}
|
||||
static inline void mi_lock_done(mi_lock_t* lock) {
|
||||
(void)(lock);
|
||||
}
|
||||
|
||||
#elif defined(MI_USE_PTHREADS)
|
||||
|
||||
#include <string.h> // memcpy
|
||||
void _mi_error_message(int err, const char* fmt, ...);
|
||||
|
||||
typedef struct mi_lock_s {
|
||||
pthread_mutex_t mutex;
|
||||
} mi_lock_t;
|
||||
|
||||
#define MI_LOCK_INITIALIZER { PTHREAD_MUTEX_INITIALIZER }
|
||||
|
||||
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
|
||||
return (pthread_mutex_trylock(&lock->mutex) == 0);
|
||||
}
|
||||
static inline void mi_lock_acquire(mi_lock_t* lock) {
|
||||
const int err = pthread_mutex_lock(&lock->mutex);
|
||||
if (err != 0) {
|
||||
_mi_error_message(err, "internal error: lock cannot be acquired (err %i)\n", err);
|
||||
}
|
||||
}
|
||||
static inline void mi_lock_release(mi_lock_t* lock) {
|
||||
pthread_mutex_unlock(&lock->mutex);
|
||||
}
|
||||
static inline void mi_lock_init(mi_lock_t* lock) {
|
||||
if(lock==NULL) return;
|
||||
// use this instead of pthread_mutex_init since that can cause allocation on some platforms (and recursively initialize)
|
||||
const pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
memcpy(&lock->mutex,&mutex,sizeof(mutex));
|
||||
}
|
||||
static inline void mi_lock_done(mi_lock_t* lock) {
|
||||
pthread_mutex_destroy(&lock->mutex);
|
||||
}
|
||||
|
||||
#elif defined(__cplusplus)
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
|
||||
typedef struct mi_lock_s {
|
||||
std::mutex mutex;
|
||||
} mi_lock_t;
|
||||
|
||||
#define MI_LOCK_INITIALIZER { }
|
||||
|
||||
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
|
||||
return lock->mutex.try_lock();
|
||||
}
|
||||
static inline void mi_lock_acquire(mi_lock_t* lock) {
|
||||
lock->mutex.lock();
|
||||
}
|
||||
static inline void mi_lock_release(mi_lock_t* lock) {
|
||||
lock->mutex.unlock();
|
||||
}
|
||||
static inline void mi_lock_init(mi_lock_t* lock) {
|
||||
new(&lock->mutex) std::mutex();
|
||||
}
|
||||
static inline void mi_lock_done(mi_lock_t* lock) {
|
||||
(void)(lock);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// fall back to poor man's locks.
|
||||
// this should only be the case in a single-threaded environment (like __wasi__)
|
||||
#include <errno.h>
|
||||
#ifndef EFAULT
|
||||
#define EFAULT (14)
|
||||
#endif
|
||||
void _mi_error_message(int err, const char* fmt, ...);
|
||||
|
||||
typedef struct mi_lock_s {
|
||||
_Atomic(uintptr_t) mutex;
|
||||
} mi_lock_t;
|
||||
|
||||
#define MI_LOCK_INITIALIZER { MI_ATOMIC_VAR_INIT(0) }
|
||||
|
||||
static inline bool mi_lock_try_acquire(mi_lock_t* lock) {
|
||||
uintptr_t expected = 0;
|
||||
return mi_atomic_cas_strong_acq_rel(&lock->mutex, &expected, (uintptr_t)1);
|
||||
}
|
||||
static inline void mi_lock_acquire(mi_lock_t* lock) {
|
||||
size_t ticks = 0;
|
||||
for (int i = 0; i < 10000; i++) { // for at most 10000 tries?
|
||||
if (mi_lock_try_acquire(lock)) return;
|
||||
mi_atomic_yield_sleep(&ticks,100);
|
||||
}
|
||||
_mi_error_message(EFAULT, "internal error: lock cannot be acquired (due to lack of native lock primitives)\n");
|
||||
}
|
||||
static inline void mi_lock_release(mi_lock_t* lock) {
|
||||
mi_atomic_store_release(&lock->mutex, (uintptr_t)0);
|
||||
}
|
||||
static inline void mi_lock_init(mi_lock_t* lock) {
|
||||
mi_lock_release(lock);
|
||||
}
|
||||
static inline void mi_lock_done(mi_lock_t* lock) {
|
||||
(void)(lock);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct mi_atomic_once_s {
|
||||
_Atomic(uintptr_t) tid;
|
||||
mi_lock_t lock;
|
||||
} mi_atomic_once_t;
|
||||
|
||||
// Returns `true` only on the first invocation, signifying we can execute an action once.
|
||||
// If it returns `true`, the caller should call `_mi_atomic_once_release` after performing the action.
|
||||
// Other threads (than the initial thread that entered) will block until `_mi_atomic_once_release` has been called.
|
||||
bool _mi_atomic_once_enter(mi_atomic_once_t* once); // defined in `libc.c`
|
||||
void _mi_atomic_once_release(mi_atomic_once_t* once); // defined in `libc.c`
|
||||
|
||||
#define mi_atomic_do_once \
|
||||
static mi_atomic_once_t _mi_once = { MI_ATOMIC_VAR_INIT(0), MI_LOCK_INITIALIZER }; \
|
||||
for(bool _mi_exec = _mi_atomic_once_enter(&_mi_once); _mi_exec; (_mi_atomic_once_release(&_mi_once),_mi_exec=false))
|
||||
|
||||
|
||||
#endif // __MIMALLOC_ATOMIC_H
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2024, 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.
|
||||
@@ -7,27 +7,29 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#pragma once
|
||||
#ifndef MIMALLOC_PRIM_H
|
||||
#define MIMALLOC_PRIM_H
|
||||
|
||||
#include "internal.h" // mi_decl_hidden
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// This file specifies the primitive portability API.
|
||||
// Each OS/host needs to implement these primitives, see `src/prim`
|
||||
// for implementations on Window, macOS, WASI, and Linux/Unix.
|
||||
//
|
||||
// note: on all primitive functions, we always have result parameters != NUL, and:
|
||||
// note: on all primitive functions, we always have result parameters != NULL, and:
|
||||
// addr != NULL and page aligned
|
||||
// size > 0 and page aligned
|
||||
// return value is an error code an int where 0 is success.
|
||||
// the return value is an error code as an `int` where 0 is success
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// OS memory configuration
|
||||
typedef struct mi_os_mem_config_s {
|
||||
size_t page_size; // 4KiB
|
||||
size_t large_page_size; // 2MiB
|
||||
size_t alloc_granularity; // smallest allocation size (on Windows 64KiB)
|
||||
bool has_overcommit; // can we reserve more memory than can be actually committed?
|
||||
bool must_free_whole; // must allocated blocks be freed as a whole (false for mmap, true for VirtualAlloc)
|
||||
bool has_virtual_reserve; // supports virtual address space reservation? (if true we can reserve virtual address space without using commit or physical memory)
|
||||
size_t page_size; // default to 4KiB
|
||||
size_t large_page_size; // 0 if not supported, usually 2MiB
|
||||
size_t alloc_granularity; // smallest allocation size (usually 4KiB, on Windows 64KiB)
|
||||
size_t physical_memory_in_kib; // physical memory size in KiB
|
||||
size_t virtual_address_bits; // usually 48 or 56 bits on 64-bit systems. (used to determine secure randomization)
|
||||
bool has_overcommit; // can we reserve more memory than can be actually committed?
|
||||
bool has_partial_free; // can allocated blocks be freed partially? (true for mmap, false for VirtualAlloc)
|
||||
bool has_virtual_reserve; // supports virtual address space reservation? (if true we can reserve virtual address space without using commit or physical memory)
|
||||
} mi_os_mem_config_t;
|
||||
|
||||
// Initialize
|
||||
@@ -41,9 +43,10 @@ int _mi_prim_free(void* addr, size_t size );
|
||||
// If `commit` is false, the virtual memory range only needs to be reserved (with no access)
|
||||
// which will later be committed explicitly using `_mi_prim_commit`.
|
||||
// `is_zero` is set to true if the memory was zero initialized (as on most OS's)
|
||||
// The `hint_addr` address is either `NULL` or a preferred allocation address but can be ignored.
|
||||
// pre: !commit => !allow_large
|
||||
// try_alignment >= _mi_os_page_size() and a power of 2
|
||||
int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr);
|
||||
int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr);
|
||||
|
||||
// Commit memory. Returns error code or 0 on success.
|
||||
// For example, on Linux this would make the memory PROT_READ|PROT_WRITE.
|
||||
@@ -56,10 +59,15 @@ int _mi_prim_commit(void* addr, size_t size, bool* is_zero);
|
||||
// pre: needs_recommit != NULL
|
||||
int _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit);
|
||||
|
||||
// Reset memory. The range keeps being accessible but the content might be reset.
|
||||
// Reset memory. The range keeps being accessible but the content might be reset to zero at any moment.
|
||||
// Returns error code or 0 on success.
|
||||
int _mi_prim_reset(void* addr, size_t size);
|
||||
|
||||
// Reuse memory. This is called for memory that is already committed but
|
||||
// may have been reset (`_mi_prim_reset`) or decommitted (`_mi_prim_decommit`) where `needs_recommit` was false.
|
||||
// Returns error code or 0 on success. On most platforms this is a no-op.
|
||||
int _mi_prim_reuse(void* addr, size_t size);
|
||||
|
||||
// Protect memory. Returns error code or 0 on success.
|
||||
int _mi_prim_protect(void* addr, size_t size, bool protect);
|
||||
|
||||
@@ -98,7 +106,9 @@ void _mi_prim_out_stderr( const char* msg );
|
||||
|
||||
// Get an environment variable. (only for options)
|
||||
// name != NULL, result != NULL, result_size >= 64
|
||||
bool _mi_prim_getenv(const char* name, char* result, size_t result_size);
|
||||
// Return 1 for success, 0 if not found,
|
||||
// and -1 on error (for example, if `getenv` cannot be called yet during preloading).
|
||||
int _mi_prim_getenv(const char* name, char* result, size_t result_size);
|
||||
|
||||
|
||||
// Fill a buffer with strong randomness; return `false` on error or if
|
||||
@@ -116,48 +126,30 @@ void _mi_prim_thread_associate_default_heap(mi_heap_t* heap);
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Thread id: `_mi_prim_thread_id()`
|
||||
//
|
||||
// Getting the thread id should be performant as it is called in the
|
||||
// fast path of `_mi_free` and we specialize for various platforms as
|
||||
// inlined definitions. Regular code should call `init.c:_mi_thread_id()`.
|
||||
// We only require _mi_prim_thread_id() to return a unique id
|
||||
// for each thread (unequal to zero).
|
||||
// Access to TLS (thread local storage) slots.
|
||||
// We need fast access to both a unique thread id (in `free.c:mi_free`) and
|
||||
// to a thread-local heap pointer (in `alloc.c:mi_malloc`).
|
||||
// To achieve this we use specialized code for various platforms.
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// defined in `init.c`; do not use these directly
|
||||
extern mi_decl_thread mi_heap_t* _mi_heap_default; // default heap to allocate from
|
||||
extern bool _mi_process_is_initialized; // has mi_process_init been called?
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept;
|
||||
|
||||
#ifdef MI_PRIM_THREAD_ID
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
return MI_PRIM_THREAD_ID();
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
// Windows: works on Intel and ARM in both 32- and 64-bit
|
||||
return (uintptr_t)NtCurrentTeb();
|
||||
}
|
||||
|
||||
// We use assembly for a fast thread id on the main platforms. The TLS layout depends on
|
||||
// both the OS and libc implementation so we use specific tests for each main platform.
|
||||
// On some libc + platform combinations we can directly access a thread-local storage (TLS) slot.
|
||||
// The TLS layout depends on both the OS and libc implementation so we use specific tests for each main platform.
|
||||
// If you test on another platform and it works please send a PR :-)
|
||||
// see also https://akkadia.org/drepper/tls.pdf for more info on the TLS register.
|
||||
#elif defined(__GNUC__) && ( \
|
||||
(defined(__GLIBC__) && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \
|
||||
|| (defined(__APPLE__) && (defined(__x86_64__) || defined(__aarch64__))) \
|
||||
//
|
||||
// Note: we would like to prefer `__builtin_thread_pointer()` nowadays instead of using assembly,
|
||||
// but unfortunately we can not detect support reliably (see issue #883)
|
||||
// We also use it on Apple OS as we use a TLS slot for the default heap there.
|
||||
#if defined(__GNUC__) && ( \
|
||||
(defined(__GLIBC__) && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__) || defined(__riscv))) \
|
||||
|| (defined(__APPLE__) && (defined(__x86_64__) || defined(__aarch64__) || defined(__POWERPC__))) \
|
||||
|| (defined(__BIONIC__) && (defined(__x86_64__) || defined(__i386__) || (defined(__arm__) && __ARM_ARCH >= 7) || defined(__aarch64__))) \
|
||||
|| (defined(__FreeBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \
|
||||
|| (defined(__OpenBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \
|
||||
)
|
||||
|
||||
#define MI_HAS_TLS_SLOT 1
|
||||
|
||||
static inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {
|
||||
void* res;
|
||||
const size_t ofs = (slot*sizeof(void*));
|
||||
@@ -181,6 +173,13 @@ static inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {
|
||||
__asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb));
|
||||
#endif
|
||||
res = tcb[slot];
|
||||
#elif defined(__riscv)
|
||||
void** tcb; MI_UNUSED(ofs);
|
||||
__asm__ volatile ("mv %0, tp" : "=r" (tcb));
|
||||
res = tcb[slot];
|
||||
#elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781
|
||||
MI_UNUSED(ofs);
|
||||
res = pthread_getspecific(slot);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
@@ -208,9 +207,113 @@ static inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexce
|
||||
__asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb));
|
||||
#endif
|
||||
tcb[slot] = value;
|
||||
#elif defined(__riscv)
|
||||
void** tcb; MI_UNUSED(ofs);
|
||||
__asm__ volatile ("mv %0, tp" : "=r" (tcb));
|
||||
tcb[slot] = value;
|
||||
#elif defined(__APPLE__) && defined(__POWERPC__) // ppc, issue #781
|
||||
MI_UNUSED(ofs);
|
||||
pthread_setspecific(slot, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
#elif _WIN32 && MI_WIN_USE_FIXED_TLS && !defined(MI_WIN_USE_FLS)
|
||||
|
||||
// On windows we can store the thread-local heap at a fixed TLS slot to avoid
|
||||
// thread-local initialization checks in the fast path.
|
||||
// We allocate a user TLS slot at process initialization (see `windows/prim.c`)
|
||||
// and store the offset `_mi_win_tls_offset`.
|
||||
#define MI_HAS_TLS_SLOT 1 // 2 = we can reliably initialize the slot (saving a test on each malloc)
|
||||
|
||||
extern mi_decl_hidden size_t _mi_win_tls_offset;
|
||||
|
||||
#if MI_WIN_USE_FIXED_TLS > 1
|
||||
#define MI_TLS_SLOT (MI_WIN_USE_FIXED_TLS)
|
||||
#elif MI_SIZE_SIZE == 4
|
||||
#define MI_TLS_SLOT (0x0E10 + _mi_win_tls_offset) // User TLS slots <https://en.wikipedia.org/wiki/Win32_Thread_Information_Block>
|
||||
#else
|
||||
#define MI_TLS_SLOT (0x1480 + _mi_win_tls_offset) // User TLS slots <https://en.wikipedia.org/wiki/Win32_Thread_Information_Block>
|
||||
#endif
|
||||
|
||||
static inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept {
|
||||
#if (_M_X64 || _M_AMD64) && !defined(_M_ARM64EC)
|
||||
return (void*)__readgsqword((unsigned long)slot); // direct load at offset from gs
|
||||
#elif _M_IX86 && !defined(_M_ARM64EC)
|
||||
return (void*)__readfsdword((unsigned long)slot); // direct load at offset from fs
|
||||
#else
|
||||
return ((void**)NtCurrentTeb())[slot / sizeof(void*)];
|
||||
#endif
|
||||
}
|
||||
static inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexcept {
|
||||
((void**)NtCurrentTeb())[slot / sizeof(void*)] = value;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get a fast unique thread id.
|
||||
//
|
||||
// Getting the thread id should be performant as it is called in the
|
||||
// fast path of `_mi_free` and we specialize for various platforms as
|
||||
// inlined definitions. Regular code should call `init.c:_mi_thread_id()`.
|
||||
// We only require _mi_prim_thread_id() to return a unique id
|
||||
// for each thread (unequal to zero).
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
|
||||
// Do we have __builtin_thread_pointer? This would be the preferred way to get a unique thread id
|
||||
// but unfortunately, it seems we cannot test for this reliably at this time (see issue #883)
|
||||
// Nevertheless, it seems needed on older graviton platforms (see issue #851).
|
||||
// For now, we only enable this for specific platforms.
|
||||
#if !defined(MI_USE_BUILTIN_THREAD_POINTER) /* allow user override */
|
||||
#if !defined(__APPLE__) /* on apple (M1) the wrong register is read (tpidr_el0 instead of tpidrro_el0) so fall back to TLS slot assembly (<https://github.com/microsoft/mimalloc/issues/343#issuecomment-763272369>)*/ \
|
||||
&& !defined(__CYGWIN__) \
|
||||
&& !defined(MI_LIBC_MUSL) \
|
||||
&& (!defined(__clang_major__) || __clang_major__ >= 14) /* older clang versions emit bad code; fall back to using the TLS slot (<https://lore.kernel.org/linux-arm-kernel/202110280952.352F66D8@keescook/T/>) */
|
||||
#if (defined(__GNUC__) && (__GNUC__ >= 7) && defined(__aarch64__)) /* aarch64 for older gcc versions (issue #851) */ \
|
||||
|| (defined(__GNUC__) && (__GNUC__ >= 7) && defined(__riscv)) \
|
||||
|| (defined(__GNUC__) && (__GNUC__ >= 11) && defined(__x86_64__)) \
|
||||
|| (defined(__clang_major__) && (__clang_major__ >= 14) && (defined(__aarch64__) || defined(__x86_64__) || defined(__riscv))) /* pr #1363 */
|
||||
#define MI_USE_BUILTIN_THREAD_POINTER 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// defined in `init.c`; do not use these directly
|
||||
extern mi_decl_hidden mi_decl_thread mi_heap_t* _mi_heap_default; // default heap to allocate from
|
||||
extern mi_decl_hidden bool _mi_process_is_initialized; // has mi_process_init been called?
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept;
|
||||
|
||||
// Get a unique id for the current thread.
|
||||
#if defined(MI_PRIM_THREAD_ID)
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
const mi_threadid_t tid = MI_PRIM_THREAD_ID(); // used for example by CPython for a free threaded build (see python/cpython#115488)
|
||||
mi_assert_internal( (tid & 0x03) == 0 ); // mimalloc reserves the bottom 2 bits
|
||||
return tid;
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
// Windows: works on Intel and ARM in both 32- and 64-bit
|
||||
return (uintptr_t)NtCurrentTeb();
|
||||
}
|
||||
|
||||
#elif MI_USE_BUILTIN_THREAD_POINTER
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
// Works on most Unix based platforms with recent compilers
|
||||
return (uintptr_t)__builtin_thread_pointer();
|
||||
}
|
||||
|
||||
#elif MI_HAS_TLS_SLOT
|
||||
|
||||
static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
#if defined(__BIONIC__)
|
||||
// issue #384, #495: on the Bionic libc (Android), slot 1 is the thread id
|
||||
@@ -236,7 +339,8 @@ static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept {
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------------------
|
||||
The thread local default heap: `_mi_prim_get_default_heap()`
|
||||
Get the thread local default heap: `_mi_prim_get_default_heap()`
|
||||
|
||||
This is inlined here as it is on the fast path for allocation functions.
|
||||
|
||||
On most platforms (Windows, Linux, FreeBSD, NetBSD, etc), this just returns a
|
||||
@@ -257,7 +361,6 @@ static inline mi_heap_t* mi_prim_get_default_heap(void);
|
||||
#if defined(MI_MALLOC_OVERRIDE)
|
||||
#if defined(__APPLE__) // macOS
|
||||
#define MI_TLS_SLOT 89 // seems unused?
|
||||
// #define MI_TLS_RECURSE_GUARD 1
|
||||
// other possible unused ones are 9, 29, __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4 (94), __PTK_FRAMEWORK_GC_KEY9 (112) and __PTK_FRAMEWORK_OLDGC_KEY9 (89)
|
||||
// see <https://github.com/rweichler/substrate/blob/master/include/pthread_machdep.h>
|
||||
#elif defined(__OpenBSD__)
|
||||
@@ -274,16 +377,21 @@ static inline mi_heap_t* mi_prim_get_default_heap(void);
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MI_TLS_SLOT)
|
||||
#if MI_TLS_SLOT
|
||||
# if !defined(MI_HAS_TLS_SLOT)
|
||||
# error "trying to use a TLS slot for the default heap, but the mi_prim_tls_slot primitives are not defined"
|
||||
# endif
|
||||
|
||||
static inline mi_heap_t* mi_prim_get_default_heap(void) {
|
||||
mi_heap_t* heap = (mi_heap_t*)mi_prim_tls_slot(MI_TLS_SLOT);
|
||||
#if MI_HAS_TLS_SLOT == 1 // check if the TLS slot is initialized
|
||||
if mi_unlikely(heap == NULL) {
|
||||
#ifdef __GNUC__
|
||||
__asm(""); // prevent conditional load of the address of _mi_heap_empty
|
||||
#endif
|
||||
heap = (mi_heap_t*)&_mi_heap_empty;
|
||||
}
|
||||
#endif
|
||||
return heap;
|
||||
}
|
||||
|
||||
@@ -307,7 +415,7 @@ static inline mi_heap_t* mi_prim_get_default_heap(void) {
|
||||
|
||||
#elif defined(MI_TLS_PTHREAD)
|
||||
|
||||
extern pthread_key_t _mi_heap_default_key;
|
||||
extern mi_decl_hidden pthread_key_t _mi_heap_default_key;
|
||||
static inline mi_heap_t* mi_prim_get_default_heap(void) {
|
||||
mi_heap_t* heap = (mi_unlikely(_mi_heap_default_key == (pthread_key_t)(-1)) ? _mi_heap_main_get() : (mi_heap_t*)pthread_getspecific(_mi_heap_default_key));
|
||||
return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap);
|
||||
@@ -325,5 +433,4 @@ static inline mi_heap_t* mi_prim_get_default_heap(void) {
|
||||
#endif // mi_prim_get_default_heap()
|
||||
|
||||
|
||||
|
||||
#endif // MIMALLOC_PRIM_H
|
||||
@@ -82,11 +82,10 @@ defined, undefined, or not accessible at all:
|
||||
#define MI_TRACK_HEAP_DESTROY 1
|
||||
#define MI_TRACK_TOOL "ETW"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include "../src/prim/windows/etw.h"
|
||||
|
||||
#define mi_track_init() EventRegistermicrosoft_windows_mimalloc();
|
||||
#define mi_track_init() EventRegistermicrosoft_windows_mimalloc()
|
||||
#define mi_track_done() EventUnregistermicrosoft_windows_mimalloc()
|
||||
#define mi_track_malloc_size(p,reqsize,size,zero) EventWriteETW_MI_ALLOC((UINT64)(p), size)
|
||||
#define mi_track_free_size(p,size) EventWriteETW_MI_FREE((UINT64)(p), size)
|
||||
|
||||
@@ -106,7 +105,7 @@ defined, undefined, or not accessible at all:
|
||||
// Utility definitions
|
||||
|
||||
#ifndef mi_track_resize
|
||||
#define mi_track_resize(p,oldsize,newsize) mi_track_free_size(p,oldsize); mi_track_malloc(p,newsize,false)
|
||||
#define mi_track_resize(p,oldsize,newsize) do{ mi_track_free_size(p,oldsize); mi_track_malloc(p,newsize,false); } while(0)
|
||||
#endif
|
||||
|
||||
#ifndef mi_track_align
|
||||
@@ -117,6 +116,10 @@ defined, undefined, or not accessible at all:
|
||||
#define mi_track_init()
|
||||
#endif
|
||||
|
||||
#ifndef mi_track_done
|
||||
#define mi_track_done()
|
||||
#endif
|
||||
|
||||
#ifndef mi_track_mem_defined
|
||||
#define mi_track_mem_defined(p,size)
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2024, 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.
|
||||
@@ -12,15 +12,20 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
// This file contains the main type definitions for mimalloc:
|
||||
// mi_heap_t : all data for a thread-local heap, contains
|
||||
// lists of all managed heap pages.
|
||||
// mi_segment_t : a larger chunk of memory (32GiB) from where pages
|
||||
// are allocated.
|
||||
// mi_page_t : a mimalloc page (usually 64KiB or 512KiB) from
|
||||
// mi_segment_t : a larger chunk of memory (32MiB on 64-bit) from where pages
|
||||
// are allocated. A segment is divided in slices (64KiB) from
|
||||
// which pages are allocated.
|
||||
// mi_page_t : a "mimalloc" page (usually 64KiB or 512KiB) from
|
||||
// where objects are allocated.
|
||||
// Note: we write "OS page" for OS memory pages while
|
||||
// using plain "page" for mimalloc pages (`mi_page_t`).
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
|
||||
#include "../mimalloc-stats.h"
|
||||
#include <stddef.h> // ptrdiff_t
|
||||
#include <stdint.h> // uintptr_t, uint16_t, etc
|
||||
#include <stdbool.h> // bool
|
||||
#include "atomic.h" // _Atomic
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -33,49 +38,6 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#define MI_MAX_ALIGN_SIZE 16 // sizeof(max_align_t)
|
||||
#endif
|
||||
|
||||
#define MI_CACHE_LINE 64
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths)
|
||||
#pragma warning(disable:26812) // unscoped enum warning
|
||||
#define mi_decl_noinline __declspec(noinline)
|
||||
#define mi_decl_thread __declspec(thread)
|
||||
#define mi_decl_cache_align __declspec(align(MI_CACHE_LINE))
|
||||
#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) // includes clang and icc
|
||||
#define mi_decl_noinline __attribute__((noinline))
|
||||
#define mi_decl_thread __thread
|
||||
#define mi_decl_cache_align __attribute__((aligned(MI_CACHE_LINE)))
|
||||
#else
|
||||
#define mi_decl_noinline
|
||||
#define mi_decl_thread __thread // hope for the best :-)
|
||||
#define mi_decl_cache_align
|
||||
#endif
|
||||
|
||||
#if (MI_DEBUG)
|
||||
#if defined(_MSC_VER)
|
||||
#define mi_decl_noreturn __declspec(noreturn)
|
||||
#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__)
|
||||
#define mi_decl_noreturn __attribute__((__noreturn__))
|
||||
#else
|
||||
#define mi_decl_noreturn
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 'cold' attribute seems to have been fully supported since GCC 4.x.
|
||||
* See https://github.com/gcc-mirror/gcc/commit/52bf96d2f299e9e6.
|
||||
*/
|
||||
#if (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__)
|
||||
#define mi_decl_cold __attribute__((cold))
|
||||
#else
|
||||
#define mi_decl_cold
|
||||
#endif
|
||||
|
||||
#if (defined(__GNUC__) && defined(__THROW))
|
||||
#define mi_decl_throw __THROW
|
||||
#else
|
||||
#define mi_decl_throw
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Variants
|
||||
// ------------------------------------------------------
|
||||
@@ -92,27 +54,33 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
// #define MI_STAT 1
|
||||
|
||||
// Define MI_SECURE to enable security mitigations
|
||||
// #define MI_SECURE 1 // guard page around metadata
|
||||
// #define MI_SECURE 2 // guard page around each mimalloc page
|
||||
// #define MI_SECURE 1 // guard pages around meta data, randomize arena allocation addresses (like ASLR), abort on detected meta data corruption
|
||||
// #define MI_SECURE 2 // randomize relative allocation addresses (within mimalloc pages)
|
||||
// #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free)
|
||||
// #define MI_SECURE 4 // checks for double free. (may be more expensive)
|
||||
// #define MI_SECURE 4 // checks for double free (may be more expensive) (`-DMI_SECURE=ON`)
|
||||
// #define MI_SECURE 5 // guard page at the end of each mimalloc page (expensive!) (`-DMI_SECURE_FULL=ON`)
|
||||
|
||||
#if !defined(MI_SECURE)
|
||||
#define MI_SECURE 0
|
||||
#endif
|
||||
|
||||
// Define MI_DEBUG for debug mode
|
||||
// #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free.
|
||||
// #define MI_DEBUG 2 // + internal assertion checks
|
||||
// Define MI_DEBUG for assertion and invariant checking
|
||||
// #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free. (cmake -DMI_DEBUG=ON)
|
||||
// #define MI_DEBUG 2 // + internal assertion checks (cmake -DMI_DEBUG_INTERNAL=ON)
|
||||
// #define MI_DEBUG 3 // + extensive internal invariant checking (cmake -DMI_DEBUG_FULL=ON)
|
||||
#if !defined(MI_DEBUG)
|
||||
#if !defined(NDEBUG) || defined(_DEBUG)
|
||||
#define MI_DEBUG 2
|
||||
#else
|
||||
#if defined(MI_BUILD_RELEASE) || defined(NDEBUG)
|
||||
#define MI_DEBUG 0
|
||||
#else
|
||||
#define MI_DEBUG 2
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Enable guard pages behind objects of a certain size (set by the MIMALLOC_GUARDED_MIN/MAX/SAMPLE_RATE options)
|
||||
#if !defined(MI_GUARDED) && MI_DEBUG
|
||||
#define MI_GUARDED 1
|
||||
#endif
|
||||
|
||||
// Reserve extra padding at the end of each block to be more resilient against heap block overflows.
|
||||
// The padding can detect buffer overflow on free.
|
||||
#if !defined(MI_PADDING) && (MI_SECURE>=3 || MI_DEBUG>=1 || (MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_TRACK_ETW))
|
||||
@@ -131,11 +99,16 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#define MI_ENCODE_FREELIST 1
|
||||
#endif
|
||||
|
||||
#if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0))
|
||||
#define MI_CHECK_DOUBLE_FREE 1
|
||||
#endif
|
||||
|
||||
// We used to abandon huge pages but to eagerly deallocate if freed from another thread,
|
||||
// but that makes it not possible to visit them during a heap walk or include them in a
|
||||
// `mi_heap_destroy`. We therefore instead reset/decommit the huge blocks if freed from
|
||||
// another thread so most memory is available until it gets properly freed by the owning thread.
|
||||
|
||||
// We used to abandon huge pages in order to eagerly deallocate it if freed from another thread.
|
||||
// Unfortunately, that makes it not possible to visit them during a heap walk or include them in a
|
||||
// `mi_heap_destroy`. We therefore instead reset/decommit the huge blocks nowadays if freed from
|
||||
// another thread so the memory becomes "virtually" available (and eventually gets properly freed by
|
||||
// the owning thread).
|
||||
// #define MI_HUGE_PAGE_ABANDON 1
|
||||
|
||||
|
||||
@@ -200,52 +173,66 @@ typedef int32_t mi_ssize_t;
|
||||
|
||||
// Main tuning parameters for segment and page sizes
|
||||
// Sizes for 64-bit (usually divide by two for 32-bit)
|
||||
#ifndef MI_SEGMENT_SLICE_SHIFT
|
||||
#define MI_SEGMENT_SLICE_SHIFT (13 + MI_INTPTR_SHIFT) // 64KiB (32KiB on 32-bit)
|
||||
#endif
|
||||
|
||||
#ifndef MI_SEGMENT_SHIFT
|
||||
#if MI_INTPTR_SIZE > 4
|
||||
#define MI_SEGMENT_SHIFT ( 9 + MI_SEGMENT_SLICE_SHIFT) // 32MiB
|
||||
#else
|
||||
#define MI_SEGMENT_SHIFT ( 7 + MI_SEGMENT_SLICE_SHIFT) // 4MiB on 32-bit
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MI_SMALL_PAGE_SHIFT
|
||||
#define MI_SMALL_PAGE_SHIFT (MI_SEGMENT_SLICE_SHIFT) // 64KiB
|
||||
#endif
|
||||
#ifndef MI_MEDIUM_PAGE_SHIFT
|
||||
#define MI_MEDIUM_PAGE_SHIFT ( 3 + MI_SMALL_PAGE_SHIFT) // 512KiB
|
||||
|
||||
#endif
|
||||
|
||||
// Derived constants
|
||||
#define MI_SEGMENT_SIZE (MI_ZU(1)<<MI_SEGMENT_SHIFT)
|
||||
#define MI_SEGMENT_ALIGN MI_SEGMENT_SIZE
|
||||
#define MI_SEGMENT_MASK ((uintptr_t)(MI_SEGMENT_ALIGN - 1))
|
||||
#define MI_SEGMENT_SLICE_SIZE (MI_ZU(1)<< MI_SEGMENT_SLICE_SHIFT)
|
||||
#define MI_SLICES_PER_SEGMENT (MI_SEGMENT_SIZE / MI_SEGMENT_SLICE_SIZE) // 1024
|
||||
#define MI_SLICES_PER_SEGMENT (MI_SEGMENT_SIZE / MI_SEGMENT_SLICE_SIZE) // 512 (128 on 32-bit)
|
||||
|
||||
#define MI_SMALL_PAGE_SIZE (MI_ZU(1)<<MI_SMALL_PAGE_SHIFT)
|
||||
#define MI_MEDIUM_PAGE_SIZE (MI_ZU(1)<<MI_MEDIUM_PAGE_SHIFT)
|
||||
|
||||
#define MI_SMALL_OBJ_SIZE_MAX (MI_SMALL_PAGE_SIZE/4) // 8KiB on 64-bit
|
||||
#define MI_MEDIUM_OBJ_SIZE_MAX (MI_MEDIUM_PAGE_SIZE/4) // 128KiB on 64-bit
|
||||
#define MI_SMALL_OBJ_SIZE_MAX (MI_SMALL_PAGE_SIZE/8) // 8 KiB on 64-bit
|
||||
#define MI_MEDIUM_OBJ_SIZE_MAX (MI_MEDIUM_PAGE_SIZE/8) // 64 KiB on 64-bit
|
||||
#define MI_MEDIUM_OBJ_WSIZE_MAX (MI_MEDIUM_OBJ_SIZE_MAX/MI_INTPTR_SIZE)
|
||||
#define MI_LARGE_OBJ_SIZE_MAX (MI_SEGMENT_SIZE/2) // 32MiB on 64-bit
|
||||
#define MI_LARGE_OBJ_SIZE_MAX (MI_SEGMENT_SIZE/2) // 16 MiB on 64-bit
|
||||
#define MI_LARGE_OBJ_WSIZE_MAX (MI_LARGE_OBJ_SIZE_MAX/MI_INTPTR_SIZE)
|
||||
|
||||
// Maximum number of size classes. (spaced exponentially in 12.5% increments)
|
||||
#define MI_BIN_HUGE (73U)
|
||||
#if MI_BIN_HUGE != 73U
|
||||
#error "mimalloc internal: expecting 73 bins"
|
||||
#endif
|
||||
|
||||
#if (MI_MEDIUM_OBJ_WSIZE_MAX >= 655360)
|
||||
#error "mimalloc internal: define more bins"
|
||||
#endif
|
||||
|
||||
// Maximum slice offset (15)
|
||||
#define MI_MAX_SLICE_OFFSET ((MI_ALIGNMENT_MAX / MI_SEGMENT_SLICE_SIZE) - 1)
|
||||
// Maximum block size for which blocks are guaranteed to be block size aligned. (see `segment.c:_mi_segment_page_start`)
|
||||
#define MI_MAX_ALIGN_GUARANTEE (MI_MEDIUM_OBJ_SIZE_MAX)
|
||||
|
||||
// Used as a special value to encode block sizes in 32 bits.
|
||||
#define MI_HUGE_BLOCK_SIZE ((uint32_t)(2*MI_GiB))
|
||||
// Alignments over MI_BLOCK_ALIGNMENT_MAX are allocated in dedicated huge page segments
|
||||
#define MI_BLOCK_ALIGNMENT_MAX (MI_SEGMENT_SIZE >> 1)
|
||||
|
||||
// blocks up to this size are always allocated aligned
|
||||
#define MI_MAX_ALIGN_GUARANTEE (8*MI_MAX_ALIGN_SIZE)
|
||||
// Maximum slice count (255) for which we can find the page for interior pointers
|
||||
#define MI_MAX_SLICE_OFFSET_COUNT ((MI_BLOCK_ALIGNMENT_MAX / MI_SEGMENT_SLICE_SIZE) - 1)
|
||||
|
||||
// Alignments over MI_ALIGNMENT_MAX are allocated in dedicated huge page segments
|
||||
#define MI_ALIGNMENT_MAX (MI_SEGMENT_SIZE >> 1)
|
||||
// we never allocate more than PTRDIFF_MAX (see also <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
|
||||
// on 64-bit+ systems we also limit the maximum allocation size such that the slice count fits in 32-bits. (issue #877)
|
||||
#if (PTRDIFF_MAX > INT32_MAX) && (PTRDIFF_MAX >= (MI_SEGMENT_SLIZE_SIZE * UINT32_MAX))
|
||||
#define MI_MAX_ALLOC_SIZE (MI_SEGMENT_SLICE_SIZE * (UINT32_MAX-1))
|
||||
#else
|
||||
#define MI_MAX_ALLOC_SIZE PTRDIFF_MAX
|
||||
#endif
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -261,16 +248,23 @@ typedef size_t mi_threadid_t;
|
||||
|
||||
// free lists contain blocks
|
||||
typedef struct mi_block_s {
|
||||
_Atomic(mi_encoded_t) next;
|
||||
_Atomic(mi_encoded_t) next; // CPython: atomic to avoid TSAN reports from concurrent heap visits (gh-129748)
|
||||
} mi_block_t;
|
||||
|
||||
#if MI_GUARDED
|
||||
// we always align guarded pointers in a block at an offset
|
||||
// the block `next` field is then used as a tag to distinguish regular offset aligned blocks from guarded ones
|
||||
#define MI_BLOCK_TAG_ALIGNED ((mi_encoded_t)(0))
|
||||
#define MI_BLOCK_TAG_GUARDED (~MI_BLOCK_TAG_ALIGNED)
|
||||
#endif
|
||||
|
||||
|
||||
// The delayed flags are used for efficient multi-threaded free-ing
|
||||
typedef enum mi_delayed_e {
|
||||
MI_USE_DELAYED_FREE = 0, // push on the owning heap thread delayed list
|
||||
MI_DELAYED_FREEING = 1, // temporary: another thread is accessing the owning heap
|
||||
MI_NO_DELAYED_FREE = 2, // optimize: push on page local thread free queue if another block is already in the heap thread delayed free list
|
||||
MI_NEVER_DELAYED_FREE = 3 // sticky, only resets on page reclaim
|
||||
MI_NEVER_DELAYED_FREE = 3 // sticky: used for abandoned pages without a owning heap; this only resets on page reclaim
|
||||
} mi_delayed_t;
|
||||
|
||||
|
||||
@@ -287,7 +281,7 @@ typedef union mi_page_flags_s {
|
||||
#else
|
||||
// under thread sanitizer, use a byte for each flag to suppress warning, issue #130
|
||||
typedef union mi_page_flags_s {
|
||||
uint16_t full_aligned;
|
||||
uint32_t full_aligned;
|
||||
struct {
|
||||
uint8_t in_full;
|
||||
uint8_t has_aligned;
|
||||
@@ -309,7 +303,6 @@ typedef uintptr_t mi_thread_free_t;
|
||||
// implement a monotonic heartbeat. The `thread_free` list is needed for
|
||||
// avoiding atomic operations in the common case.
|
||||
//
|
||||
//
|
||||
// `used - |thread_free|` == actual blocks that are in use (alive)
|
||||
// `used - |thread_free| + |free| + |local_free| == capacity`
|
||||
//
|
||||
@@ -317,16 +310,13 @@ typedef uintptr_t mi_thread_free_t;
|
||||
// the number of memory accesses in the `mi_page_all_free` function(s).
|
||||
//
|
||||
// Notes:
|
||||
// - Access is optimized for `mi_free` and `mi_page_alloc` (in `alloc.c`)
|
||||
// - Access is optimized for `free.c:mi_free` and `alloc.c:mi_page_alloc`
|
||||
// - Using `uint16_t` does not seem to slow things down
|
||||
// - The size is 8 words on 64-bit which helps the page index calculations
|
||||
// (and 10 words on 32-bit, and encoded free lists add 2 words. Sizes 10
|
||||
// and 12 are still good for address calculation)
|
||||
// - To limit the structure size, the `xblock_size` is 32-bits only; for
|
||||
// blocks > MI_HUGE_BLOCK_SIZE the size is determined from the segment page size
|
||||
// - `thread_free` uses the bottom bits as a delayed-free flags to optimize
|
||||
// - The size is 12 words on 64-bit which helps the page index calculations
|
||||
// (and 14 words on 32-bit, and encoded free lists add 2 words)
|
||||
// - `xthread_free` uses the bottom bits as a delayed-free flags to optimize
|
||||
// concurrent frees where only the first concurrent free adds to the owning
|
||||
// heap `thread_delayed_free` list (see `alloc.c:mi_free_block_mt`).
|
||||
// heap `thread_delayed_free` list (see `free.c:mi_free_block_mt`).
|
||||
// The invariant is that no-delayed-free is only set if there is
|
||||
// at least one block that will be added, or as already been added, to
|
||||
// the owning heap `thread_delayed_free` list. This guarantees that pages
|
||||
@@ -335,23 +325,27 @@ typedef struct mi_page_s {
|
||||
// "owned" by the segment
|
||||
uint32_t slice_count; // slices in this page (0 if not a page)
|
||||
uint32_t slice_offset; // distance from the actual page data slice (0 if a page)
|
||||
uint8_t is_committed : 1; // `true` if the page virtual memory is committed
|
||||
uint8_t is_zero_init : 1; // `true` if the page was initially zero initialized
|
||||
uint8_t use_qsbr : 1; // delay page freeing using qsbr
|
||||
uint8_t tag : 4; // tag from the owning heap
|
||||
uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory
|
||||
|
||||
uint8_t is_committed:1; // `true` if the page virtual memory is committed
|
||||
uint8_t is_zero_init:1; // `true` if the page was initially zero initialized
|
||||
uint8_t is_huge:1; // `true` if the page is in a huge segment (`segment->kind == MI_SEGMENT_HUGE`)
|
||||
// padding
|
||||
// layout like this to optimize access in `mi_malloc` and `mi_free`
|
||||
uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear`
|
||||
uint16_t reserved; // number of blocks reserved in memory
|
||||
mi_page_flags_t flags; // `in_full` and `has_aligned` flags (8 bits)
|
||||
uint8_t free_is_zero : 1; // `true` if the blocks in the free list are zero initialized
|
||||
uint8_t retire_expire : 7; // expiration count for retired blocks
|
||||
uint8_t free_is_zero:1; // `true` if the blocks in the free list are zero initialized
|
||||
uint8_t retire_expire:7; // expiration count for retired blocks
|
||||
|
||||
mi_block_t* free; // list of available free blocks (`malloc` allocates from this list)
|
||||
uint32_t used; // number of blocks in use (including blocks in `local_free` and `thread_free`)
|
||||
uint32_t xblock_size; // size available in each block (always `>0`)
|
||||
mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`)
|
||||
uint16_t used; // number of blocks in use (including blocks in `thread_free`)
|
||||
uint8_t block_size_shift; // if not zero, then `(1 << block_size_shift) == block_size` (only used for fast path in `free.c:_mi_page_ptr_unalign`)
|
||||
uint8_t heap_tag; // tag of the owning heap, used to separate heaps by object type
|
||||
uint8_t use_qsbr:1; // CPython: delay page freeing using QSBR
|
||||
uint8_t debug_offset; // CPython: number of bytes to preserve when filling freed or uninitialized memory
|
||||
// padding
|
||||
size_t block_size; // size available in each block (always `>0`)
|
||||
uint8_t* page_start; // start of the page area containing the blocks
|
||||
|
||||
#if (MI_ENCODE_FREELIST || MI_PADDING)
|
||||
uintptr_t keys[2]; // two random keys to encode the free lists (see `_mi_block_next`) or padding canary
|
||||
@@ -364,14 +358,12 @@ typedef struct mi_page_s {
|
||||
struct mi_page_s* prev; // previous page owned by this thread with the same `block_size`
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
struct llist_node qsbr_node;
|
||||
uint64_t qsbr_goal;
|
||||
struct llist_node qsbr_node; // CPython: list of pages waiting for a QSBR goal before being freed
|
||||
uint64_t qsbr_goal; // CPython: QSBR goal to reach before the page can be freed (0 if none)
|
||||
#else
|
||||
// 64-bit 11 words, 32-bit 13 words, (+2 for secure)
|
||||
void* padding[1];
|
||||
#endif
|
||||
|
||||
// 64-bit 9 words, 32-bit 12 words, (+2 for secure)
|
||||
#if MI_INTPTR_SIZE==8 && !defined(Py_GIL_DISABLED)
|
||||
uintptr_t padding[1];
|
||||
#endif
|
||||
} mi_page_t;
|
||||
|
||||
|
||||
@@ -382,14 +374,15 @@ typedef struct mi_page_s {
|
||||
|
||||
typedef enum mi_page_kind_e {
|
||||
MI_PAGE_SMALL, // small blocks go into 64KiB pages inside a segment
|
||||
MI_PAGE_MEDIUM, // medium blocks go into medium pages inside a segment
|
||||
MI_PAGE_LARGE, // larger blocks go into a page of just one block
|
||||
MI_PAGE_HUGE, // huge blocks (> 16 MiB) are put into a single page in a single segment.
|
||||
MI_PAGE_MEDIUM, // medium blocks go into 512KiB pages inside a segment
|
||||
MI_PAGE_LARGE, // larger blocks go into a single page spanning a whole segment
|
||||
MI_PAGE_HUGE // a huge page is a single page in a segment of variable size
|
||||
// used for blocks `> MI_LARGE_OBJ_SIZE_MAX` or an alignment `> MI_BLOCK_ALIGNMENT_MAX`.
|
||||
} mi_page_kind_t;
|
||||
|
||||
typedef enum mi_segment_kind_e {
|
||||
MI_SEGMENT_NORMAL, // MI_SEGMENT_SIZE size with pages inside.
|
||||
MI_SEGMENT_HUGE, // > MI_LARGE_SIZE_MAX segment with just one huge page inside.
|
||||
MI_SEGMENT_HUGE, // segment with just one huge page inside.
|
||||
} mi_segment_kind_t;
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -422,13 +415,17 @@ typedef mi_page_t mi_slice_t;
|
||||
typedef int64_t mi_msecs_t;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// a memory id tracks the provenance of arena/OS allocated memory
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// Memory can reside in arena's, direct OS allocated, or statically allocated. The memid keeps track of this.
|
||||
typedef enum mi_memkind_e {
|
||||
MI_MEM_NONE, // not allocated
|
||||
MI_MEM_EXTERNAL, // not owned by mimalloc but provided externally (via `mi_manage_os_memory` for example)
|
||||
MI_MEM_STATIC, // allocated in a static area and should not be freed (for arena meta data for example)
|
||||
MI_MEM_OS, // allocated from the OS
|
||||
MI_MEM_OS_HUGE, // allocated as huge os pages
|
||||
MI_MEM_OS_HUGE, // allocated as huge OS pages (usually 1GiB, pinned to physical memory)
|
||||
MI_MEM_OS_REMAP, // allocated in a remapable area (i.e. using `mremap`)
|
||||
MI_MEM_ARENA // allocated from an arena (the usual case)
|
||||
} mi_memkind_t;
|
||||
@@ -439,13 +436,13 @@ static inline bool mi_memkind_is_os(mi_memkind_t memkind) {
|
||||
|
||||
typedef struct mi_memid_os_info {
|
||||
void* base; // actual base address of the block (used for offset aligned allocations)
|
||||
size_t alignment; // alignment at allocation
|
||||
size_t size; // full allocation size
|
||||
} mi_memid_os_info_t;
|
||||
|
||||
typedef struct mi_memid_arena_info {
|
||||
size_t block_index; // index in the arena
|
||||
mi_arena_id_t id; // arena id (>= 1)
|
||||
bool is_exclusive; // the arena can only be used for specific arena allocations
|
||||
bool is_exclusive; // this arena can only be used for specific arena allocations
|
||||
} mi_memid_arena_info_t;
|
||||
|
||||
typedef struct mi_memid_s {
|
||||
@@ -453,71 +450,67 @@ typedef struct mi_memid_s {
|
||||
mi_memid_os_info_t os; // only used for MI_MEM_OS
|
||||
mi_memid_arena_info_t arena; // only used for MI_MEM_ARENA
|
||||
} mem;
|
||||
bool is_pinned; // `true` if we cannot decommit/reset/protect in this memory (e.g. when allocated using large OS pages)
|
||||
bool is_pinned; // `true` if we cannot decommit/reset/protect in this memory (e.g. when allocated using large (2Mib) or huge (1GiB) OS pages)
|
||||
bool initially_committed;// `true` if the memory was originally allocated as committed
|
||||
bool initially_zero; // `true` if the memory was originally zero initialized
|
||||
mi_memkind_t memkind;
|
||||
} mi_memid_t;
|
||||
|
||||
|
||||
// Segments are large allocated memory blocks (8mb on 64 bit) from
|
||||
// the OS. Inside segments we allocated fixed size _pages_ that
|
||||
// contain blocks.
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Segments are large allocated memory blocks (32mb on 64 bit) from arenas or the OS.
|
||||
//
|
||||
// Inside segments we allocated fixed size mimalloc pages (`mi_page_t`) that contain blocks.
|
||||
// The start of a segment is this structure with a fixed number of slice entries (`slices`)
|
||||
// usually followed by a guard OS page and the actual allocation area with pages.
|
||||
// While a page is not allocated, we view it's data as a `mi_slice_t` (instead of a `mi_page_t`).
|
||||
// Of any free area, the first slice has the info and `slice_offset == 0`; for any subsequent
|
||||
// slices part of the area, the `slice_offset` is the byte offset back to the first slice
|
||||
// (so we can quickly find the page info on a free, `internal.h:_mi_segment_page_of`).
|
||||
// For slices, the `block_size` field is repurposed to signify if a slice is used (`1`) or not (`0`).
|
||||
// Small and medium pages use a fixed amount of slices to reduce slice fragmentation, while
|
||||
// large and huge pages span a variable amount of slices.
|
||||
|
||||
typedef struct mi_subproc_s mi_subproc_t;
|
||||
|
||||
typedef struct mi_segment_s {
|
||||
// constant fields
|
||||
mi_memid_t memid; // memory id for arena allocation
|
||||
bool allow_decommit;
|
||||
bool allow_purge;
|
||||
mi_memid_t memid; // memory id for arena/OS allocation
|
||||
bool allow_decommit; // can we decommit the memory
|
||||
bool allow_purge; // can we purge the memory (reset or decommit)
|
||||
size_t segment_size;
|
||||
mi_subproc_t* subproc; // segment belongs to sub process
|
||||
|
||||
// segment fields
|
||||
mi_msecs_t purge_expire;
|
||||
mi_commit_mask_t purge_mask;
|
||||
mi_commit_mask_t commit_mask;
|
||||
|
||||
_Atomic(struct mi_segment_s*) abandoned_next;
|
||||
mi_msecs_t purge_expire; // purge slices in the `purge_mask` after this time
|
||||
mi_commit_mask_t purge_mask; // slices that can be purged
|
||||
mi_commit_mask_t commit_mask; // slices that are currently committed
|
||||
|
||||
// from here is zero initialized
|
||||
struct mi_segment_s* next; // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`)
|
||||
bool was_reclaimed; // true if it was reclaimed (used to limit on-free reclamation)
|
||||
bool dont_free; // can be temporarily true to ensure the segment is not freed
|
||||
bool free_is_zero; // if free spans are zero
|
||||
|
||||
size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`)
|
||||
size_t abandoned_visits; // count how often this segment is visited in the abandoned list (to force reclaim if it is too long)
|
||||
size_t abandoned_visits; // count how often this segment is visited during abandoned reclamation (to force reclaim if it takes too long)
|
||||
size_t used; // count of pages in use
|
||||
uintptr_t cookie; // verify addresses in debug mode: `mi_ptr_cookie(segment) == segment->cookie`
|
||||
|
||||
struct mi_segment_s* abandoned_os_next; // only used for abandoned segments outside arena's, and only if `mi_option_visit_abandoned` is enabled
|
||||
struct mi_segment_s* abandoned_os_prev;
|
||||
|
||||
size_t segment_slices; // for huge segments this may be different from `MI_SLICES_PER_SEGMENT`
|
||||
size_t segment_info_slices; // initial slices we are using segment info and possible guard pages.
|
||||
size_t segment_info_slices; // initial count of slices that we are using for segment info and possible guard pages.
|
||||
|
||||
// layout like this to optimize access in `mi_free`
|
||||
mi_segment_kind_t kind;
|
||||
size_t slice_entries; // entries in the `slices` array, at most `MI_SLICES_PER_SEGMENT`
|
||||
_Atomic(mi_threadid_t) thread_id; // unique id of the thread owning this segment
|
||||
|
||||
mi_slice_t slices[MI_SLICES_PER_SEGMENT+1]; // one more for huge blocks with large alignment
|
||||
mi_slice_t slices[MI_SLICES_PER_SEGMENT+1]; // one extra final entry for huge blocks with large alignment
|
||||
} mi_segment_t;
|
||||
|
||||
typedef uintptr_t mi_tagged_segment_t;
|
||||
|
||||
// Segments unowned by any thread are put in a shared pool
|
||||
typedef struct mi_abandoned_pool_s {
|
||||
// This is a list of visited abandoned pages that were full at the time.
|
||||
// this list migrates to `abandoned` when that becomes NULL. The use of
|
||||
// this list reduces contention and the rate at which segments are visited.
|
||||
mi_decl_cache_align _Atomic(mi_segment_t*) abandoned_visited; // = NULL
|
||||
|
||||
// The abandoned page list (tagged as it supports pop)
|
||||
mi_decl_cache_align _Atomic(mi_tagged_segment_t) abandoned; // = NULL
|
||||
|
||||
// Maintain these for debug purposes (these counts may be a bit off)
|
||||
mi_decl_cache_align _Atomic(size_t) abandoned_count;
|
||||
mi_decl_cache_align _Atomic(size_t) abandoned_visited_count;
|
||||
|
||||
// We also maintain a count of current readers of the abandoned list
|
||||
// in order to prevent resetting/decommitting segment memory if it might
|
||||
// still be read.
|
||||
mi_decl_cache_align _Atomic(size_t) abandoned_readers; // = 0
|
||||
} mi_abandoned_pool_t;
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Heaps
|
||||
@@ -557,7 +550,7 @@ typedef struct mi_random_cxt_s {
|
||||
#if (MI_PADDING)
|
||||
typedef struct mi_padding_s {
|
||||
uint32_t canary; // encoded block value to check validity of the padding (in case of overflow)
|
||||
uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes)
|
||||
uint32_t delta; // padding bytes before the block. (mi_full_usable_size(p) - delta == exact allocated bytes)
|
||||
} mi_padding_t;
|
||||
#define MI_PADDING_SIZE (sizeof(mi_padding_t))
|
||||
#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE)
|
||||
@@ -572,8 +565,6 @@ typedef struct mi_padding_s {
|
||||
// A heap owns a set of pages.
|
||||
struct mi_heap_s {
|
||||
mi_tld_t* tld;
|
||||
mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.
|
||||
mi_page_queue_t pages[MI_BIN_FULL + 1]; // queue of pages for each size class (or "bin")
|
||||
_Atomic(mi_block_t*) thread_delayed_free;
|
||||
mi_threadid_t thread_id; // thread this heap belongs too
|
||||
mi_arena_id_t arena_id; // arena id if the heap belongs to a specific arena (or 0)
|
||||
@@ -583,14 +574,78 @@ struct mi_heap_s {
|
||||
size_t page_count; // total number of pages in the `pages` queues.
|
||||
size_t page_retired_min; // smallest retired index (retired pages are fully free, but still in the page queues)
|
||||
size_t page_retired_max; // largest retired index into the `pages` array.
|
||||
size_t pages_full_size; // optimization: total size of blocks in the pages of the full queue (issue #1220)
|
||||
long generic_count; // how often is `_mi_malloc_generic` called?
|
||||
long generic_collect_count; // how often is `_mi_malloc_generic` called without collecting?
|
||||
mi_heap_t* next; // list of heaps per thread
|
||||
bool no_reclaim; // `true` if this heap should not reclaim abandoned pages
|
||||
uint8_t tag; // custom identifier for this heap
|
||||
uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory
|
||||
bool page_use_qsbr; // should freeing pages be delayed using QSBR
|
||||
uint8_t tag; // custom tag, can be used for separating heaps based on the object types
|
||||
uint8_t debug_offset; // CPython: number of bytes to preserve when filling freed or uninitialized memory
|
||||
bool page_use_qsbr; // CPython: should freeing pages be delayed using QSBR
|
||||
#if MI_GUARDED
|
||||
size_t guarded_size_min; // minimal size for guarded objects
|
||||
size_t guarded_size_max; // maximal size for guarded objects
|
||||
size_t guarded_sample_rate; // sample rate (set to 0 to disable guarded pages)
|
||||
size_t guarded_sample_count; // current sample count (counting down to 0)
|
||||
#endif
|
||||
mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size.
|
||||
mi_page_queue_t pages[MI_BIN_FULL + 1]; // queue of pages for each size class (or "bin")
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Sub processes do not reclaim or visit segments
|
||||
// from other sub processes. These are essentially the
|
||||
// static variables of a process.
|
||||
// ------------------------------------------------------
|
||||
|
||||
struct mi_subproc_s {
|
||||
_Atomic(size_t) abandoned_count; // count of abandoned segments for this sub-process
|
||||
_Atomic(size_t) abandoned_os_list_count; // count of abandoned segments in the os-list
|
||||
mi_lock_t abandoned_os_lock; // lock for the abandoned os segment list (outside of arena's) (this lock protect list operations)
|
||||
mi_lock_t abandoned_os_visit_lock; // ensure only one thread per subproc visits the abandoned os list
|
||||
mi_segment_t* abandoned_os_list; // doubly-linked list of abandoned segments outside of arena's (in OS allocated memory)
|
||||
mi_segment_t* abandoned_os_list_tail; // the tail-end of the list
|
||||
mi_memid_t memid; // provenance of this memory block
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Thread Local data
|
||||
// ------------------------------------------------------
|
||||
|
||||
// A "span" is is an available range of slices. The span queues keep
|
||||
// track of slice spans of at most the given `slice_count` (but more than the previous size class).
|
||||
typedef struct mi_span_queue_s {
|
||||
mi_slice_t* first;
|
||||
mi_slice_t* last;
|
||||
size_t slice_count;
|
||||
} mi_span_queue_t;
|
||||
|
||||
#define MI_SEGMENT_BIN_MAX (35) // 35 == mi_segment_bin(MI_SLICES_PER_SEGMENT)
|
||||
|
||||
// Segments thread local data
|
||||
typedef struct mi_segments_tld_s {
|
||||
mi_span_queue_t spans[MI_SEGMENT_BIN_MAX+1]; // free slice spans inside segments
|
||||
size_t count; // current number of segments;
|
||||
size_t peak_count; // peak number of segments
|
||||
size_t current_size; // current size of all segments
|
||||
size_t peak_size; // peak size of all segments
|
||||
size_t reclaim_count;// number of reclaimed (abandoned) segments
|
||||
mi_subproc_t* subproc; // sub-process this thread belongs to.
|
||||
mi_stats_t* stats; // points to tld stats
|
||||
} mi_segments_tld_t;
|
||||
|
||||
// Thread local data
|
||||
struct mi_tld_s {
|
||||
unsigned long long heartbeat; // monotonic heartbeat count
|
||||
bool recurse; // true if deferred was called; used to prevent infinite recursion.
|
||||
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
|
||||
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
|
||||
mi_segments_tld_t segments; // segment tld
|
||||
mi_stats_t stats; // statistics
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Debug
|
||||
@@ -606,31 +661,10 @@ struct mi_heap_s {
|
||||
#define MI_DEBUG_PADDING (0xDE)
|
||||
#endif
|
||||
|
||||
#if (MI_DEBUG)
|
||||
// use our own assertion to print without memory allocation
|
||||
mi_decl_noreturn mi_decl_cold
|
||||
void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func) mi_decl_throw;
|
||||
#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))
|
||||
#else
|
||||
#define mi_assert(x)
|
||||
#endif
|
||||
|
||||
#if (MI_DEBUG>1)
|
||||
#define mi_assert_internal mi_assert
|
||||
#else
|
||||
#define mi_assert_internal(x)
|
||||
#endif
|
||||
|
||||
#if (MI_DEBUG>2)
|
||||
#define mi_assert_expensive mi_assert
|
||||
#else
|
||||
#define mi_assert_expensive(x)
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Statistics
|
||||
// ------------------------------------------------------
|
||||
|
||||
#ifndef MI_STAT
|
||||
#if (MI_DEBUG>0)
|
||||
#define MI_STAT 2
|
||||
@@ -639,110 +673,28 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct mi_stat_count_s {
|
||||
int64_t allocated;
|
||||
int64_t freed;
|
||||
int64_t peak;
|
||||
int64_t current;
|
||||
} mi_stat_count_t;
|
||||
|
||||
typedef struct mi_stat_counter_s {
|
||||
int64_t total;
|
||||
int64_t count;
|
||||
} mi_stat_counter_t;
|
||||
|
||||
typedef struct mi_stats_s {
|
||||
mi_stat_count_t segments;
|
||||
mi_stat_count_t pages;
|
||||
mi_stat_count_t reserved;
|
||||
mi_stat_count_t committed;
|
||||
mi_stat_count_t reset;
|
||||
mi_stat_count_t purged;
|
||||
mi_stat_count_t page_committed;
|
||||
mi_stat_count_t segments_abandoned;
|
||||
mi_stat_count_t pages_abandoned;
|
||||
mi_stat_count_t threads;
|
||||
mi_stat_count_t normal;
|
||||
mi_stat_count_t huge;
|
||||
mi_stat_count_t large;
|
||||
mi_stat_count_t malloc;
|
||||
mi_stat_count_t segments_cache;
|
||||
mi_stat_counter_t pages_extended;
|
||||
mi_stat_counter_t mmap_calls;
|
||||
mi_stat_counter_t commit_calls;
|
||||
mi_stat_counter_t reset_calls;
|
||||
mi_stat_counter_t purge_calls;
|
||||
mi_stat_counter_t page_no_retire;
|
||||
mi_stat_counter_t searches;
|
||||
mi_stat_counter_t normal_count;
|
||||
mi_stat_counter_t huge_count;
|
||||
mi_stat_counter_t large_count;
|
||||
#if MI_STAT>1
|
||||
mi_stat_count_t normal_bins[MI_BIN_HUGE+1];
|
||||
#endif
|
||||
} mi_stats_t;
|
||||
|
||||
|
||||
// add to stat keeping track of the peak
|
||||
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount);
|
||||
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount);
|
||||
void _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount);
|
||||
// counters can just be increased
|
||||
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
|
||||
|
||||
#if (MI_STAT)
|
||||
#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount)
|
||||
#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount)
|
||||
#define mi_stat_adjust_decrease(stat,amount) _mi_stat_adjust_decrease( &(stat), amount)
|
||||
#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount)
|
||||
#else
|
||||
#define mi_stat_increase(stat,amount) (void)0
|
||||
#define mi_stat_decrease(stat,amount) (void)0
|
||||
#define mi_stat_counter_increase(stat,amount) (void)0
|
||||
#define mi_stat_increase(stat,amount) ((void)0)
|
||||
#define mi_stat_decrease(stat,amount) ((void)0)
|
||||
#define mi_stat_adjust_decrease(stat,amount) ((void)0)
|
||||
#define mi_stat_counter_increase(stat,amount) ((void)0)
|
||||
#endif
|
||||
|
||||
#define mi_heap_stat_counter_increase(heap,stat,amount) mi_stat_counter_increase( (heap)->tld->stats.stat, amount)
|
||||
#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount)
|
||||
#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount)
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Thread Local data
|
||||
// ------------------------------------------------------
|
||||
|
||||
// A "span" is an available range of slices. The span queues keep
|
||||
// track of slice spans of at most the given `slice_count` (but more than the previous size class).
|
||||
typedef struct mi_span_queue_s {
|
||||
mi_slice_t* first;
|
||||
mi_slice_t* last;
|
||||
size_t slice_count;
|
||||
} mi_span_queue_t;
|
||||
|
||||
#define MI_SEGMENT_BIN_MAX (35) // 35 == mi_segment_bin(MI_SLICES_PER_SEGMENT)
|
||||
|
||||
// OS thread local data
|
||||
typedef struct mi_os_tld_s {
|
||||
size_t region_idx; // start point for next allocation
|
||||
mi_stats_t* stats; // points to tld stats
|
||||
} mi_os_tld_t;
|
||||
|
||||
|
||||
// Segments thread local data
|
||||
typedef struct mi_segments_tld_s {
|
||||
mi_span_queue_t spans[MI_SEGMENT_BIN_MAX+1]; // free slice spans inside segments
|
||||
size_t count; // current number of segments;
|
||||
size_t peak_count; // peak number of segments
|
||||
size_t current_size; // current size of all segments
|
||||
size_t peak_size; // peak size of all segments
|
||||
mi_stats_t* stats; // points to tld stats
|
||||
mi_os_tld_t* os; // points to os stats
|
||||
mi_abandoned_pool_t* abandoned; // pool of abandoned segments
|
||||
} mi_segments_tld_t;
|
||||
|
||||
// Thread local data
|
||||
struct mi_tld_s {
|
||||
unsigned long long heartbeat; // monotonic heartbeat count
|
||||
bool recurse; // true if deferred was called; used to prevent infinite recursion.
|
||||
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
|
||||
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
|
||||
mi_segments_tld_t segments; // segment tld
|
||||
mi_os_tld_t os; // os tld
|
||||
mi_stats_t stats; // statistics
|
||||
};
|
||||
#define mi_heap_stat_adjust_decrease(heap,stat,amount) mi_stat_adjust_decrease( (heap)->tld->stats.stat, amount)
|
||||
|
||||
#endif
|
||||
@@ -22,10 +22,16 @@ typedef enum {
|
||||
#ifdef WITH_MIMALLOC
|
||||
# ifdef Py_GIL_DISABLED
|
||||
# define MI_PRIM_THREAD_ID _Py_ThreadId
|
||||
# define MI_VISIT_ABANDONED 1
|
||||
# endif
|
||||
# define MI_DEBUG_UNINIT PYMEM_CLEANBYTE
|
||||
# define MI_DEBUG_FREED PYMEM_DEADBYTE
|
||||
# define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE
|
||||
// Do not use transparent huge pages (THP). They increase RSS on Linux.
|
||||
// See https://github.com/microsoft/mimalloc/blob/v2.5.2/readme.md#environment-options
|
||||
# define MI_NO_THP 1
|
||||
// Do not use guarded allocations. The free-threaded GC does not support their block layout.
|
||||
# define MI_GUARDED 0
|
||||
#ifdef Py_DEBUG
|
||||
# define MI_DEBUG 2
|
||||
#else
|
||||
@@ -52,9 +58,11 @@ extern "C++" {
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
struct _mimalloc_interp_state {
|
||||
// When exiting, threads place any segments with live blocks in this
|
||||
// shared pool for other threads to claim and reuse.
|
||||
mi_abandoned_pool_t abandoned_pool;
|
||||
// When exiting, threads abandon any segments with live blocks. The
|
||||
// abandoned segments are tracked per mimalloc sub-process so that other
|
||||
// threads of the same interpreter can claim and reuse them, and so that
|
||||
// the GC can visit them.
|
||||
mi_subproc_t *subproc;
|
||||
};
|
||||
|
||||
struct _mimalloc_thread_state {
|
||||
|
||||
+10
-2
@@ -375,6 +375,7 @@ IO_OBJS= \
|
||||
MIMALLOC_HEADERS= \
|
||||
$(srcdir)/Include/internal/pycore_mimalloc.h \
|
||||
$(srcdir)/Include/internal/mimalloc/mimalloc.h \
|
||||
$(srcdir)/Include/internal/mimalloc/mimalloc-stats.h \
|
||||
$(srcdir)/Include/internal/mimalloc/mimalloc/atomic.h \
|
||||
$(srcdir)/Include/internal/mimalloc/mimalloc/internal.h \
|
||||
$(srcdir)/Include/internal/mimalloc/mimalloc/prim.h \
|
||||
@@ -2084,9 +2085,12 @@ Objects/obmalloc.o: $(srcdir)/Objects/mimalloc/alloc.c \
|
||||
$(srcdir)/Objects/mimalloc/alloc-aligned.c \
|
||||
$(srcdir)/Objects/mimalloc/alloc-posix.c \
|
||||
$(srcdir)/Objects/mimalloc/arena.c \
|
||||
$(srcdir)/Objects/mimalloc/arena-abandon.c \
|
||||
$(srcdir)/Objects/mimalloc/bitmap.c \
|
||||
$(srcdir)/Objects/mimalloc/free.c \
|
||||
$(srcdir)/Objects/mimalloc/heap.c \
|
||||
$(srcdir)/Objects/mimalloc/init.c \
|
||||
$(srcdir)/Objects/mimalloc/libc.c \
|
||||
$(srcdir)/Objects/mimalloc/options.c \
|
||||
$(srcdir)/Objects/mimalloc/os.c \
|
||||
$(srcdir)/Objects/mimalloc/page.c \
|
||||
@@ -2095,6 +2099,7 @@ Objects/obmalloc.o: $(srcdir)/Objects/mimalloc/alloc.c \
|
||||
$(srcdir)/Objects/mimalloc/segment-map.c \
|
||||
$(srcdir)/Objects/mimalloc/stats.c \
|
||||
$(srcdir)/Objects/mimalloc/prim/prim.c \
|
||||
$(srcdir)/Objects/mimalloc/prim/emscripten/prim.c \
|
||||
$(srcdir)/Objects/mimalloc/prim/osx/prim.c \
|
||||
$(srcdir)/Objects/mimalloc/prim/unix/prim.c \
|
||||
$(srcdir)/Objects/mimalloc/prim/wasi/prim.c
|
||||
@@ -2928,8 +2933,11 @@ inclinstall:
|
||||
$(INSTALL_DATA) $$i $(DESTDIR)$(INCLUDEPY)/internal; \
|
||||
done
|
||||
@if test "$(INSTALL_MIMALLOC)" = "yes"; then \
|
||||
echo $(INSTALL_DATA) $(srcdir)/Include/internal/mimalloc/mimalloc.h $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc.h; \
|
||||
$(INSTALL_DATA) $(srcdir)/Include/internal/mimalloc/mimalloc.h $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc.h; \
|
||||
for i in $(srcdir)/Include/internal/mimalloc/*.h; \
|
||||
do \
|
||||
echo $(INSTALL_DATA) $$i $(INCLUDEPY)/internal/mimalloc; \
|
||||
$(INSTALL_DATA) $$i $(DESTDIR)$(INCLUDEPY)/internal/mimalloc; \
|
||||
done; \
|
||||
for i in $(srcdir)/Include/internal/mimalloc/mimalloc/*.h; \
|
||||
do \
|
||||
echo $(INSTALL_DATA) $$i $(INCLUDEPY)/internal/mimalloc/mimalloc; \
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Update mimalloc version from 2.1.2 to 2.5.2. Patch by Donghee Na.
|
||||
@@ -15,118 +15,208 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
// Aligned Allocation
|
||||
// ------------------------------------------------------
|
||||
|
||||
// Fallback primitive aligned allocation -- split out for better codegen
|
||||
static mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_fallback(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero) mi_attr_noexcept
|
||||
{
|
||||
mi_assert_internal(size <= PTRDIFF_MAX);
|
||||
mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment));
|
||||
static bool mi_malloc_is_naturally_aligned( size_t size, size_t alignment ) {
|
||||
// objects up to `MI_MAX_ALIGN_GUARANTEE` are allocated aligned to their size (see `segment.c:_mi_segment_page_start`).
|
||||
mi_assert_internal(mi_alignment_is_valid(alignment));
|
||||
if (alignment > size) return false;
|
||||
if (alignment <= MI_MAX_ALIGN_SIZE) return true;
|
||||
const size_t bsize = mi_good_size(size);
|
||||
return (bsize <= MI_MAX_ALIGN_GUARANTEE && (bsize & (alignment-1)) == 0);
|
||||
}
|
||||
|
||||
const uintptr_t align_mask = alignment - 1; // for any x, `(x & align_mask) == (x % alignment)`
|
||||
const size_t padsize = size + MI_PADDING_SIZE;
|
||||
|
||||
// use regular allocation if it is guaranteed to fit the alignment constraints
|
||||
if (offset==0 && alignment<=padsize && padsize<=MI_MAX_ALIGN_GUARANTEE && (padsize&align_mask)==0) {
|
||||
void* p = _mi_heap_malloc_zero(heap, size, zero);
|
||||
mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0);
|
||||
return p;
|
||||
#if MI_GUARDED
|
||||
static mi_decl_noinline mi_decl_restrict void* mi_heap_malloc_guarded_aligned(mi_heap_t* heap, size_t size, size_t alignment, bool zero, mi_page_t** ppage) mi_attr_noexcept {
|
||||
// use over allocation for guarded blocksl
|
||||
mi_assert_internal(alignment > 0 && alignment < MI_BLOCK_ALIGNMENT_MAX);
|
||||
if mi_unlikely(alignment >= MI_BLOCK_ALIGNMENT_MAX || size > (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE - alignment)) {
|
||||
_mi_error_message(EOVERFLOW, "(guarded) aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment);
|
||||
return NULL;
|
||||
}
|
||||
const size_t oversize = size + alignment - 1;
|
||||
void* const base = _mi_heap_malloc_guarded(heap, oversize, zero, ppage);
|
||||
if (base==NULL) return NULL;
|
||||
void* const p = _mi_align_up_ptr(base, alignment);
|
||||
mi_track_align(base, p, (uint8_t*)p - (uint8_t*)base, size);
|
||||
mi_assert_internal(mi_usable_size(p) >= size);
|
||||
mi_assert_internal(_mi_is_aligned(p, alignment));
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* mi_heap_malloc_zero_no_guarded(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) {
|
||||
const size_t rate = heap->guarded_sample_rate;
|
||||
// only write if `rate!=0` so we don't write to the constant `_mi_heap_empty`
|
||||
if (rate != 0) { heap->guarded_sample_rate = 0; }
|
||||
void* p = _mi_heap_malloc_zero_ex(heap, size, zero, 0, ppage);
|
||||
if (rate != 0) { heap->guarded_sample_rate = rate; }
|
||||
return p;
|
||||
}
|
||||
#else
|
||||
static void* mi_heap_malloc_zero_no_guarded(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) {
|
||||
return _mi_heap_malloc_zero_ex(heap, size, zero, 0, ppage);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fallback aligned allocation that over-allocates -- split out for better codegen
|
||||
static mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_overalloc(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero, mi_page_t** ppage) mi_attr_noexcept
|
||||
{
|
||||
mi_assert_internal(size <= (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE));
|
||||
mi_assert_internal(mi_alignment_is_valid(alignment));
|
||||
|
||||
void* p;
|
||||
size_t oversize;
|
||||
if mi_unlikely(alignment > MI_ALIGNMENT_MAX) {
|
||||
mi_page_t* page;
|
||||
if mi_unlikely(alignment > MI_BLOCK_ALIGNMENT_MAX) {
|
||||
// use OS allocation for very large alignment and allocate inside a huge page (dedicated segment with 1 page)
|
||||
// This can support alignments >= MI_SEGMENT_SIZE by ensuring the object can be aligned at a point in the
|
||||
// first (and single) page such that the segment info is `MI_SEGMENT_SIZE` bytes before it (so it can be found by aligning the pointer down)
|
||||
if mi_unlikely(offset != 0) {
|
||||
// todo: cannot support offset alignment for very large alignments yet
|
||||
#if MI_DEBUG > 0
|
||||
_mi_error_message(EOVERFLOW, "aligned allocation with a very large alignment cannot be used with an alignment offset (size %zu, alignment %zu, offset %zu)\n", size, alignment, offset);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
oversize = (size <= MI_SMALL_SIZE_MAX ? MI_SMALL_SIZE_MAX + 1 /* ensure we use generic malloc path */ : size);
|
||||
p = _mi_heap_malloc_zero_ex(heap, oversize, false, alignment); // the page block size should be large enough to align in the single huge page block
|
||||
// note: no guarded as alignment > 0
|
||||
p = _mi_heap_malloc_zero_ex(heap, oversize, false, alignment, &page); // the page block size should be large enough to align in the single huge page block
|
||||
// zero afterwards as only the area from the aligned_p may be committed!
|
||||
if (p == NULL) return NULL;
|
||||
}
|
||||
else {
|
||||
// otherwise over-allocate
|
||||
oversize = size + alignment - 1;
|
||||
p = _mi_heap_malloc_zero(heap, oversize, zero);
|
||||
mi_assert_internal(size <= (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) && alignment <= MI_BLOCK_ALIGNMENT_MAX);
|
||||
mi_assert_internal(size < SIZE_MAX - alignment); // `oversize` cannot overflow
|
||||
oversize = (size < MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : size) + alignment - 1; // adjust for size <= 16; with size 0 and alignment 64k, we would allocate a 64k block and pointing just beyond that.
|
||||
p = mi_heap_malloc_zero_no_guarded(heap, oversize, zero, &page);
|
||||
if (p == NULL) return NULL;
|
||||
}
|
||||
mi_assert_internal(page == _mi_ptr_page(p));
|
||||
if (ppage!=NULL) { *ppage = page; }
|
||||
|
||||
// .. and align within the allocation
|
||||
const uintptr_t align_mask = alignment - 1; // for any x, `(x & align_mask) == (x % alignment)`
|
||||
const uintptr_t poffset = ((uintptr_t)p + offset) & align_mask;
|
||||
const uintptr_t adjust = (poffset == 0 ? 0 : alignment - poffset);
|
||||
mi_assert_internal(adjust < alignment);
|
||||
void* aligned_p = (void*)((uintptr_t)p + adjust);
|
||||
if (aligned_p != p) {
|
||||
mi_page_t* page = _mi_ptr_page(p);
|
||||
mi_page_set_has_aligned(page, true);
|
||||
// if (pblock_size!=NULL) {
|
||||
// mi_assert_internal(*pblock_size > adjust);
|
||||
// if (*pblock_size > adjust) { *pblock_size = *pblock_size - adjust; }
|
||||
// mi_assert_internal(*pblock_size >= size);
|
||||
// }
|
||||
#if MI_GUARDED
|
||||
// set tag to aligned so mi_usable_size works with guard pages
|
||||
if (adjust >= sizeof(mi_block_t)) {
|
||||
mi_block_t* const block = (mi_block_t*)p;
|
||||
block->next = MI_BLOCK_TAG_ALIGNED;
|
||||
}
|
||||
#endif
|
||||
_mi_padding_shrink(page, (mi_block_t*)p, adjust + size);
|
||||
}
|
||||
// todo: expand padding if overallocated ?
|
||||
|
||||
mi_assert_internal(mi_page_usable_block_size(_mi_ptr_page(p)) >= adjust + size);
|
||||
mi_assert_internal(p == _mi_page_ptr_unalign(_mi_ptr_segment(aligned_p), _mi_ptr_page(aligned_p), aligned_p));
|
||||
mi_assert_internal(mi_page_usable_block_size(page) >= adjust + size);
|
||||
mi_assert_internal(((uintptr_t)aligned_p + offset) % alignment == 0);
|
||||
mi_assert_internal(mi_usable_size(aligned_p)>=size);
|
||||
mi_assert_internal(mi_usable_size(p) == mi_usable_size(aligned_p)+adjust);
|
||||
#if MI_DEBUG > 1
|
||||
mi_page_t* const apage = _mi_ptr_page(aligned_p);
|
||||
void* unalign_p = _mi_page_ptr_unalign(apage, aligned_p);
|
||||
mi_assert_internal(p == unalign_p);
|
||||
#endif
|
||||
|
||||
// now zero the block if needed
|
||||
if (alignment > MI_ALIGNMENT_MAX) {
|
||||
// for the tracker, on huge aligned allocations only from the start of the large block is defined
|
||||
if (alignment > MI_BLOCK_ALIGNMENT_MAX) {
|
||||
// for the tracker, on huge aligned allocations only the memory from the start of the large block is defined
|
||||
mi_track_mem_undefined(aligned_p, size);
|
||||
if (zero) {
|
||||
_mi_memzero_aligned(aligned_p, mi_usable_size(aligned_p));
|
||||
_mi_memzero(aligned_p, mi_usable_size(aligned_p)); // cannot be aligned due to abitrary offset... (todo: require offset to be a multiple of sizeof(void*)?)
|
||||
}
|
||||
}
|
||||
|
||||
if (p != aligned_p) {
|
||||
mi_track_align(p,aligned_p,adjust,mi_usable_size(aligned_p));
|
||||
#if MI_GUARDED
|
||||
mi_track_mem_defined(p, sizeof(mi_block_t));
|
||||
#endif
|
||||
}
|
||||
return aligned_p;
|
||||
}
|
||||
|
||||
// Primitive aligned allocation
|
||||
static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero) mi_attr_noexcept
|
||||
// Generic primitive aligned allocation -- split out for better codegen
|
||||
static mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_generic(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero, mi_page_t** ppage) mi_attr_noexcept
|
||||
{
|
||||
// note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size.
|
||||
if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) { // require power-of-two (see <https://en.cppreference.com/w/c/memory/aligned_alloc>)
|
||||
#if MI_DEBUG > 0
|
||||
_mi_error_message(EOVERFLOW, "aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu)\n", size, alignment);
|
||||
#endif
|
||||
mi_assert_internal(mi_alignment_is_valid(alignment));
|
||||
// we don't allocate more than MI_MAX_ALLOC_SIZE (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
|
||||
if mi_unlikely(size > (MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE)) {
|
||||
_mi_error_message(EINVAL, "aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if mi_unlikely(size > PTRDIFF_MAX) { // we don't allocate more than PTRDIFF_MAX (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
|
||||
#if MI_DEBUG > 0
|
||||
_mi_error_message(EOVERFLOW, "aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)`
|
||||
const size_t padsize = size + MI_PADDING_SIZE; // note: cannot overflow due to earlier size > PTRDIFF_MAX check
|
||||
|
||||
// try first if there happens to be a small block available with just the right alignment
|
||||
if mi_likely(padsize <= MI_SMALL_SIZE_MAX && alignment <= padsize) {
|
||||
mi_page_t* page = _mi_heap_get_free_small_page(heap, padsize);
|
||||
const bool is_aligned = (((uintptr_t)page->free+offset) & align_mask)==0;
|
||||
if mi_likely(page->free != NULL && is_aligned)
|
||||
{
|
||||
#if MI_STAT>1
|
||||
mi_heap_stat_increase(heap, malloc, size);
|
||||
#endif
|
||||
void* p = _mi_page_malloc(heap, page, padsize, zero); // TODO: inline _mi_page_malloc
|
||||
mi_assert_internal(p != NULL);
|
||||
mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);
|
||||
mi_track_malloc(p,size,zero);
|
||||
// use regular allocation if it is guaranteed to fit the alignment constraints.
|
||||
// this is important to try as the fast path in `mi_heap_malloc_zero_aligned` only works when there exist
|
||||
// a page with the right block size, and if we always use the over-alloc fallback that would never happen.
|
||||
if (offset == 0 && mi_malloc_is_naturally_aligned(size,alignment)) {
|
||||
void* p = mi_heap_malloc_zero_no_guarded(heap, size, zero, ppage);
|
||||
mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0);
|
||||
const bool is_aligned_or_null = (((uintptr_t)p) & (alignment-1))==0;
|
||||
if mi_likely(is_aligned_or_null) {
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
// this should never happen if the `mi_malloc_is_naturally_aligned` check is correct..
|
||||
mi_assert(false);
|
||||
mi_free(p);
|
||||
}
|
||||
}
|
||||
// fallback
|
||||
return mi_heap_malloc_zero_aligned_at_fallback(heap, size, alignment, offset, zero);
|
||||
|
||||
// fall back to over-allocation
|
||||
return mi_heap_malloc_zero_aligned_at_overalloc(heap,size,alignment,offset,zero,ppage);
|
||||
}
|
||||
|
||||
|
||||
static mi_decl_cold mi_decl_noinline void* mi_error_bad_alignment(size_t size, size_t alignment, size_t offset) {
|
||||
_mi_error_message(EINVAL, "aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu, offset %zu)\n", size, alignment, offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Primitive aligned allocation
|
||||
static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t size,
|
||||
const size_t alignment, const size_t offset, const bool zero,
|
||||
mi_page_t** ppage) mi_attr_noexcept
|
||||
{
|
||||
// note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size.
|
||||
if mi_unlikely(!mi_alignment_is_valid(alignment)) { // require power-of-two and multiple of void* (see <https://en.cppreference.com/w/c/memory/aligned_alloc#Notes>)
|
||||
return mi_error_bad_alignment(size, alignment, offset);
|
||||
}
|
||||
|
||||
#if MI_GUARDED
|
||||
if (offset==0 && alignment < MI_BLOCK_ALIGNMENT_MAX && mi_heap_malloc_use_guarded(heap,size)) {
|
||||
return mi_heap_malloc_guarded_aligned(heap, size, alignment, zero, ppage);
|
||||
}
|
||||
#endif
|
||||
|
||||
// try first if there happens to be a small block available with just the right alignment
|
||||
if mi_likely(size <= MI_SMALL_SIZE_MAX && alignment <= size) {
|
||||
const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)`
|
||||
const size_t padsize = size + MI_PADDING_SIZE;
|
||||
mi_page_t* page = _mi_heap_get_free_small_page(heap, padsize);
|
||||
if mi_likely(page->free != NULL) {
|
||||
const bool is_aligned = (((uintptr_t)page->free + offset) & align_mask)==0;
|
||||
if mi_likely(is_aligned)
|
||||
{
|
||||
if (ppage!=NULL) { *ppage = page; }
|
||||
void* p = (zero ? _mi_page_malloc_zeroed(heap,page,padsize) : _mi_page_malloc(heap,page,padsize)); // call specific page malloc for better codegen
|
||||
mi_assert_internal(p != NULL);
|
||||
mi_assert_internal(((uintptr_t)p + offset) % alignment == 0);
|
||||
mi_track_malloc(p,size,zero);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to generic aligned allocation
|
||||
return mi_heap_malloc_zero_aligned_at_generic(heap, size, alignment, offset, zero, ppage);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,31 +225,16 @@ static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t
|
||||
// ------------------------------------------------------
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
|
||||
return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, false);
|
||||
return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, false, NULL);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept {
|
||||
if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) return NULL;
|
||||
#if !MI_PADDING
|
||||
// without padding, any small sized allocation is naturally aligned (see also `_mi_segment_page_start`)
|
||||
if mi_likely(_mi_is_power_of_two(size) && size >= alignment && size <= MI_SMALL_SIZE_MAX)
|
||||
#else
|
||||
// with padding, we can only guarantee this for fixed alignments
|
||||
if mi_likely((alignment == sizeof(void*) || (alignment == MI_MAX_ALIGN_SIZE && size > (MI_MAX_ALIGN_SIZE/2)))
|
||||
&& size <= MI_SMALL_SIZE_MAX)
|
||||
#endif
|
||||
{
|
||||
// fast path for common alignment and size
|
||||
return mi_heap_malloc_small(heap, size);
|
||||
}
|
||||
else {
|
||||
return mi_heap_malloc_aligned_at(heap, size, alignment, 0);
|
||||
}
|
||||
return mi_heap_malloc_aligned_at(heap, size, alignment, 0);
|
||||
}
|
||||
|
||||
// ensure a definition is emitted
|
||||
#if defined(__cplusplus)
|
||||
static void* _mi_heap_malloc_aligned = (void*)&mi_heap_malloc_aligned;
|
||||
void* _mi_extern_heap_malloc_aligned = (void*)&mi_heap_malloc_aligned;
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -167,7 +242,7 @@ static void* _mi_heap_malloc_aligned = (void*)&mi_heap_malloc_aligned;
|
||||
// ------------------------------------------------------
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
|
||||
return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, true);
|
||||
return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, true, NULL);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept {
|
||||
@@ -192,6 +267,13 @@ mi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t a
|
||||
return mi_heap_malloc_aligned(mi_prim_get_default_heap(), size, alignment);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* pblock_size) mi_attr_noexcept {
|
||||
mi_page_t* page;
|
||||
void* p = mi_heap_malloc_zero_aligned_at(mi_prim_get_default_heap(), size, alignment, 0, false, &page);
|
||||
if (p!=NULL && pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
|
||||
return p;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
|
||||
return mi_heap_zalloc_aligned_at(mi_prim_get_default_heap(), size, alignment, offset);
|
||||
}
|
||||
@@ -200,6 +282,13 @@ mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t a
|
||||
return mi_heap_zalloc_aligned(mi_prim_get_default_heap(), size, alignment);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* pblock_size) mi_attr_noexcept {
|
||||
mi_page_t* page;
|
||||
void* p = mi_heap_malloc_zero_aligned_at(mi_prim_get_default_heap(), size, alignment, 0, true, &page);
|
||||
if (p!=NULL && pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
|
||||
return p;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept {
|
||||
return mi_heap_calloc_aligned_at(mi_prim_get_default_heap(), count, size, alignment, offset);
|
||||
}
|
||||
@@ -214,24 +303,30 @@ mi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t
|
||||
// ------------------------------------------------------
|
||||
|
||||
static void* mi_heap_realloc_zero_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset, bool zero) mi_attr_noexcept {
|
||||
mi_assert(alignment > 0);
|
||||
if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero);
|
||||
if (p == NULL) return mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,zero);
|
||||
size_t size = mi_usable_size(p);
|
||||
if (newsize <= size && newsize >= (size - (size / 2))
|
||||
&& (((uintptr_t)p + offset) % alignment) == 0) {
|
||||
mi_assert(mi_alignment_is_valid(alignment));
|
||||
if mi_unlikely(!mi_alignment_is_valid(alignment)) { // require power-of-two (see <https://en.cppreference.com/w/c/memory/aligned_alloc>)
|
||||
return mi_error_bad_alignment(newsize,alignment,offset);
|
||||
}
|
||||
if (alignment <= sizeof(uintptr_t) && offset==0) return _mi_heap_realloc_zero(heap,p,newsize,zero,NULL,NULL);
|
||||
if (p == NULL) return mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,zero,NULL);
|
||||
const size_t size = mi_usable_size(p);
|
||||
if (newsize <= size && newsize >= (size - (size / 2)) && (((uintptr_t)p + offset) & (alignment-1)) == 0) {
|
||||
return p; // reallocation still fits, is aligned and not more than 50% waste
|
||||
}
|
||||
else {
|
||||
// note: we don't zero allocate upfront so we only zero initialize the expanded part
|
||||
void* newp = mi_heap_malloc_aligned_at(heap,newsize,alignment,offset);
|
||||
// note: we don't zero allocate upfront so we only zero initialize the expanded part (at the cost of calling mi_usable_size)
|
||||
mi_page_t* newpage;
|
||||
void* const newp = mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,false /* no zero*/, &newpage);
|
||||
if (newp != NULL) {
|
||||
if (zero && newsize > size) {
|
||||
const size_t copy_size = (newsize > size ? size : newsize);
|
||||
const size_t zero_start = (copy_size >= sizeof(intptr_t) ? copy_size - sizeof(intptr_t) : 0); // also set last word in the previous allocation to zero to ensure any padding is zero-initialized
|
||||
const size_t usable = _mi_page_usable_size(newpage,newp);
|
||||
mi_assert_internal(usable >= newsize); // use usable for zero'ing, issue #763
|
||||
if (zero && usable > zero_start) {
|
||||
// also set last word in the previous allocation to zero to ensure any padding is zero-initialized
|
||||
size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0);
|
||||
_mi_memzero((uint8_t*)newp + start, newsize - start);
|
||||
_mi_memzero((uint8_t*)newp + zero_start, usable - zero_start);
|
||||
}
|
||||
_mi_memcpy_aligned(newp, p, (newsize > size ? size : newsize));
|
||||
_mi_memcpy(newp, p, copy_size); // cannot be aligned due to abitrary offset... (todo: require offset to be a multiple of sizeof(void*)?)
|
||||
mi_free(p); // only free if successful
|
||||
}
|
||||
return newp;
|
||||
@@ -240,9 +335,8 @@ static void* mi_heap_realloc_zero_aligned_at(mi_heap_t* heap, void* p, size_t ne
|
||||
|
||||
static void* mi_heap_realloc_zero_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, bool zero) mi_attr_noexcept {
|
||||
mi_assert(alignment > 0);
|
||||
if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero);
|
||||
size_t offset = ((uintptr_t)p % alignment); // use offset of previous allocation (p can be NULL)
|
||||
return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,zero);
|
||||
if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero,NULL,NULL);
|
||||
return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,0,zero);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept {
|
||||
@@ -296,3 +390,5 @@ mi_decl_nodiscard void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t
|
||||
mi_decl_nodiscard void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept {
|
||||
return mi_heap_recalloc_aligned(mi_prim_get_default_heap(), p, newcount, size, alignment);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2021, Microsoft Research, Daan Leijen
|
||||
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.
|
||||
@@ -9,11 +9,8 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#error "this file should be included from 'alloc.c' (so aliases can work)"
|
||||
#endif
|
||||
|
||||
#if defined(MI_MALLOC_OVERRIDE) && defined(_WIN32) && !(defined(MI_SHARED_LIB) && defined(_DLL))
|
||||
#error "It is only possible to override "malloc" on Windows when building as a DLL (and linking the C runtime as a DLL)"
|
||||
#endif
|
||||
|
||||
#if defined(MI_MALLOC_OVERRIDE) && !(defined(_WIN32))
|
||||
#if defined(MI_MALLOC_OVERRIDE) && !defined(_DLL)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
@@ -23,7 +20,7 @@ mi_decl_externc size_t malloc_good_size(size_t size);
|
||||
#endif
|
||||
|
||||
// helper definition for C override of C++ new
|
||||
typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
typedef void* mi_nothrow_t;
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Override system malloc
|
||||
@@ -71,22 +68,20 @@ typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
#define MI_INTERPOSE_FUN(oldfun,newfun) { (const void*)&newfun, (const void*)&oldfun }
|
||||
#define MI_INTERPOSE_MI(fun) MI_INTERPOSE_FUN(fun,mi_##fun)
|
||||
|
||||
__attribute__((used)) static struct mi_interpose_s _mi_interposes[] __attribute__((section("__DATA, __interpose"))) =
|
||||
#define MI_INTERPOSE_DECLS(name) __attribute__((used)) static struct mi_interpose_s name[] __attribute__((section("__DATA, __interpose")))
|
||||
|
||||
MI_INTERPOSE_DECLS(_mi_interposes) =
|
||||
{
|
||||
MI_INTERPOSE_MI(malloc),
|
||||
MI_INTERPOSE_MI(calloc),
|
||||
MI_INTERPOSE_MI(realloc),
|
||||
MI_INTERPOSE_MI(strdup),
|
||||
MI_INTERPOSE_MI(strndup),
|
||||
MI_INTERPOSE_MI(realpath),
|
||||
MI_INTERPOSE_MI(posix_memalign),
|
||||
MI_INTERPOSE_MI(reallocf),
|
||||
MI_INTERPOSE_MI(valloc),
|
||||
MI_INTERPOSE_FUN(malloc_size,mi_malloc_size_checked),
|
||||
MI_INTERPOSE_MI(malloc_good_size),
|
||||
#if defined(MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
|
||||
MI_INTERPOSE_MI(aligned_alloc),
|
||||
#endif
|
||||
#ifdef MI_OSX_ZONE
|
||||
// we interpose malloc_default_zone in alloc-override-osx.c so we can use mi_free safely
|
||||
MI_INTERPOSE_MI(free),
|
||||
@@ -97,6 +92,12 @@ typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
MI_INTERPOSE_FUN(vfree,mi_cfree),
|
||||
#endif
|
||||
};
|
||||
#if defined(MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
MI_INTERPOSE_DECLS(_mi_interposes_10_7) = { MI_INTERPOSE_MI(strndup) };
|
||||
#endif
|
||||
#if defined(MAC_OS_X_VERSION_10_15) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15)
|
||||
MI_INTERPOSE_DECLS(_mi_interposes_10_15) = { MI_INTERPOSE_MI(aligned_alloc) };
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -125,14 +126,104 @@ typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
};
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
// cannot override malloc unless using a dll.
|
||||
// we just override new/delete which does work in a static library.
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRT_HYBRIDPATCHABLE
|
||||
void* __cdecl _expand(_Pre_notnull_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Size) {
|
||||
return mi__expand(_Block, _Size);
|
||||
}
|
||||
_Check_return_ _ACRTIMP
|
||||
size_t __cdecl _msize_base(_Pre_notnull_ void* _Block) _CRT_NOEXCEPT {
|
||||
return mi_malloc_size(_Block);
|
||||
}
|
||||
_Check_return_ _ACRTIMP _CRT_HYBRIDPATCHABLE
|
||||
size_t __cdecl _msize(_Pre_notnull_ void* _Block) {
|
||||
return mi_malloc_size(_Block);
|
||||
}
|
||||
_ACRTIMP
|
||||
void __cdecl _free_base(_Pre_maybenull_ _Post_invalid_ void* _Block) {
|
||||
mi_free(_Block);
|
||||
}
|
||||
_ACRTIMP _CRT_HYBRIDPATCHABLE
|
||||
void __cdecl free(_Pre_maybenull_ _Post_invalid_ void* _Block) {
|
||||
mi_free(_Block);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRT_JIT_INTRINSIC _CRTRESTRICT _CRT_HYBRIDPATCHABLE
|
||||
void* __cdecl malloc(_In_ _CRT_GUARDOVERFLOW size_t _Size) {
|
||||
return mi_malloc(_Size);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _malloc_base(_In_ size_t _Size) {
|
||||
return mi_malloc(_Size);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _realloc_base(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ size_t _Size) {
|
||||
return mi_realloc(_Block, _Size);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT _CRT_HYBRIDPATCHABLE
|
||||
void* __cdecl realloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Size) {
|
||||
return mi_realloc(_Block, _Size);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _calloc_base(_In_ size_t _Count, _In_ size_t _Size) {
|
||||
return mi_calloc(_Count, _Size);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRT_JIT_INTRINSIC _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl calloc(_In_ _CRT_GUARDOVERFLOW size_t _Count, _In_ _CRT_GUARDOVERFLOW size_t _Size) {
|
||||
return mi_calloc(_Count, _Size);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _recalloc_base(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ size_t _Count, _In_ size_t _Size) {
|
||||
return mi_recalloc(_Block, _Count, _Size);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _recalloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Count, _In_ _CRT_GUARDOVERFLOW size_t _Size) {
|
||||
return mi_recalloc(_Block, _Count, _Size);
|
||||
}
|
||||
_ACRTIMP
|
||||
void __cdecl _aligned_free(_Pre_maybenull_ _Post_invalid_ void* _Block) {
|
||||
mi_free(_Block);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_malloc(_In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment) {
|
||||
return mi_malloc_aligned(_Size, _Alignment);
|
||||
}
|
||||
_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_offset_malloc(_In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment, _In_ size_t _Offset) {
|
||||
return mi_malloc_aligned_at(_Size, _Alignment, _Offset);
|
||||
}
|
||||
_Check_return_ _ACRTIMP
|
||||
size_t __cdecl _aligned_msize(_Pre_notnull_ void* _Block, _In_ size_t _Alignment, _In_ size_t _Offset) {
|
||||
MI_UNUSED(_Alignment); MI_UNUSED(_Offset); return mi_malloc_size(_Block);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_offset_realloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment, _In_ size_t _Offset) {
|
||||
return mi_realloc_aligned_at(_Block, _Size, _Alignment, _Offset);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_offset_recalloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Count, _In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment, _In_ size_t _Offset) {
|
||||
return mi_recalloc_aligned_at(_Block, _Count, _Size, _Alignment, _Offset);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_realloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment) {
|
||||
return mi_realloc_aligned(_Block, _Size, _Alignment);
|
||||
}
|
||||
_Success_(return != 0) _Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(_Count * _Size) _ACRTIMP _CRTALLOCATOR _CRTRESTRICT
|
||||
void* __cdecl _aligned_recalloc(_Pre_maybenull_ _Post_invalid_ void* _Block, _In_ _CRT_GUARDOVERFLOW size_t _Count, _In_ _CRT_GUARDOVERFLOW size_t _Size, _In_ size_t _Alignment) {
|
||||
return mi_recalloc_aligned(_Block, _Count, _Size, _Alignment);
|
||||
}
|
||||
#else
|
||||
// On all other systems forward to our API
|
||||
// On all other systems forward allocation primitives to our API
|
||||
mi_decl_export void* malloc(size_t size) MI_FORWARD1(mi_malloc, size)
|
||||
mi_decl_export void* calloc(size_t size, size_t n) MI_FORWARD2(mi_calloc, size, n)
|
||||
mi_decl_export void* realloc(void* p, size_t newsize) MI_FORWARD2(mi_realloc, p, newsize)
|
||||
mi_decl_export void free(void* p) MI_FORWARD0(mi_free, p)
|
||||
// In principle we do not need to forward `strdup`/`strndup` but on some systems these do not use `malloc` internally (but a more primitive call)
|
||||
// We only override if `strdup` is not a macro (as on some older libc's, see issue #885)
|
||||
#if !defined(strdup)
|
||||
mi_decl_export char* strdup(const char* str) MI_FORWARD1(mi_strdup, str)
|
||||
#endif
|
||||
#if !defined(strndup) && (!defined(__APPLE__) || (defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7))
|
||||
mi_decl_export char* strndup(const char* str, size_t n) MI_FORWARD2(mi_strndup, str, n)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__)
|
||||
@@ -192,12 +283,18 @@ typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
void _ZdaPv(void* p) MI_FORWARD0(mi_free,p) // delete[]
|
||||
void _ZdlPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n)
|
||||
void _ZdaPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n)
|
||||
|
||||
void _ZdlPvSt11align_val_t(void* p, size_t al) { mi_free_aligned(p,al); }
|
||||
void _ZdaPvSt11align_val_t(void* p, size_t al) { mi_free_aligned(p,al); }
|
||||
void _ZdlPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); }
|
||||
void _ZdaPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); }
|
||||
|
||||
#if (MI_INTPTR_SIZE==8)
|
||||
void _ZdlPvRKSt9nothrow_t(void* p, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free(p); } // operator delete(void*, std::nothrow_t const&)
|
||||
void _ZdaPvRKSt9nothrow_t(void* p, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free(p); } // operator delete[](void*, std::nothrow_t const&)
|
||||
void _ZdlPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete(void*, std::align_val_t, std::nothrow_t const&)
|
||||
void _ZdaPvSt11align_val_tRKSt9nothrow_t(void* p, size_t al, mi_nothrow_t tag) { MI_UNUSED(tag); mi_free_aligned(p,al); } // operator delete[](void*, std::align_val_t, std::nothrow_t const&)
|
||||
|
||||
#if (MI_INTPTR_SIZE==8) || (MI_INTPTR_SIZE==4 && defined(__EMSCRIPTEN__)) // pr #1257
|
||||
void* _Znwm(size_t n) MI_FORWARD1(mi_new,n) // new 64-bit
|
||||
void* _Znam(size_t n) MI_FORWARD1(mi_new,n) // new[] 64-bit
|
||||
void* _ZnwmRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { MI_UNUSED(tag); return mi_new_nothrow(n); }
|
||||
@@ -228,14 +325,25 @@ typedef struct mi_nothrow_s { int _tag; } mi_nothrow_t;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// defined here instead alloc-posix so we can alias it
|
||||
mi_decl_nodiscard size_t mi_malloc_size(const void* p) mi_attr_noexcept {
|
||||
// if (!mi_is_in_heap_region(p)) return 0;
|
||||
return mi_usable_size(p);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept {
|
||||
// if (!mi_is_in_heap_region(p)) return 0;
|
||||
return mi_usable_size(p);
|
||||
}
|
||||
|
||||
#ifndef MI_OSX_IS_INTERPOSED
|
||||
// Forward Posix/Unix calls as well
|
||||
void* reallocf(void* p, size_t newsize) MI_FORWARD2(mi_reallocf,p,newsize)
|
||||
size_t malloc_size(const void* p) MI_FORWARD1(mi_usable_size,p)
|
||||
#if !defined(__ANDROID__) && !defined(__FreeBSD__)
|
||||
size_t malloc_usable_size(void *p) MI_FORWARD1(mi_usable_size,p)
|
||||
size_t malloc_size(const void* p) MI_FORWARD1(mi_malloc_size,p)
|
||||
#if !defined(__ANDROID__) && !defined(__FreeBSD__) && !defined(__DragonFly__)
|
||||
size_t malloc_usable_size(void *p) MI_FORWARD1(mi_malloc_usable_size,p)
|
||||
#else
|
||||
size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_usable_size,p)
|
||||
size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_malloc_usable_size,p)
|
||||
#endif
|
||||
|
||||
// No forwarding here due to aliasing/name mangling issues
|
||||
@@ -259,10 +367,13 @@ extern "C" {
|
||||
// no forwarding here due to aliasing/name mangling issues
|
||||
void cfree(void* p) { mi_free(p); }
|
||||
void* pvalloc(size_t size) { return mi_pvalloc(size); }
|
||||
void* reallocarray(void* p, size_t count, size_t size) { return mi_reallocarray(p, count, size); }
|
||||
int reallocarr(void* p, size_t count, size_t size) { return mi_reallocarr(p, count, size); }
|
||||
void* memalign(size_t alignment, size_t size) { return mi_memalign(alignment, size); }
|
||||
void* _aligned_malloc(size_t alignment, size_t size) { return mi_aligned_alloc(alignment, size); }
|
||||
#if !defined(_WIN32)
|
||||
void* _aligned_malloc(size_t size, size_t alignment) { return mi_malloc_aligned(size,alignment); }
|
||||
#endif
|
||||
void* reallocarray(void* p, size_t count, size_t size) { return mi_reallocarray(p, count, size); }
|
||||
// some systems define reallocarr so mark it as a weak symbol (#751)
|
||||
mi_decl_weak int reallocarr(void* p, size_t count, size_t size) { return mi_reallocarr(p, count, size); }
|
||||
|
||||
#if defined(__wasi__)
|
||||
// forward __libc interface (see PR #667)
|
||||
@@ -272,8 +383,8 @@ void* _aligned_malloc(size_t alignment, size_t size) { return mi_aligned_allo
|
||||
void __libc_free(void* p) MI_FORWARD0(mi_free, p)
|
||||
void* __libc_memalign(size_t alignment, size_t size) { return mi_memalign(alignment, size); }
|
||||
|
||||
#elif defined(__GLIBC__) && defined(__linux__)
|
||||
// forward __libc interface (needed for glibc-based Linux distributions)
|
||||
#elif defined(__linux__)
|
||||
// forward __libc interface (needed for glibc-based and musl-based Linux distributions)
|
||||
void* __libc_malloc(size_t size) MI_FORWARD1(mi_malloc,size)
|
||||
void* __libc_calloc(size_t count, size_t size) MI_FORWARD2(mi_calloc,count,size)
|
||||
void* __libc_realloc(void* p, size_t size) MI_FORWARD2(mi_realloc,p,size)
|
||||
@@ -294,4 +405,4 @@ void* _aligned_malloc(size_t alignment, size_t size) { return mi_aligned_allo
|
||||
#pragma GCC visibility pop
|
||||
#endif
|
||||
|
||||
#endif // MI_MALLOC_OVERRIDE && !_WIN32
|
||||
#endif // MI_MALLOC_OVERRIDE
|
||||
@@ -31,17 +31,6 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#define ENOMEM 12
|
||||
#endif
|
||||
|
||||
|
||||
mi_decl_nodiscard size_t mi_malloc_size(const void* p) mi_attr_noexcept {
|
||||
// if (!mi_is_in_heap_region(p)) return 0;
|
||||
return mi_usable_size(p);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept {
|
||||
// if (!mi_is_in_heap_region(p)) return 0;
|
||||
return mi_usable_size(p);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard size_t mi_malloc_good_size(size_t size) mi_attr_noexcept {
|
||||
return mi_good_size(size);
|
||||
}
|
||||
@@ -52,23 +41,22 @@ void mi_cfree(void* p) mi_attr_noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept {
|
||||
int mi_posix_memalign(void** p, size_t alignment, size_t size) { // mi_attr_noexcept (issue #794)
|
||||
// Note: The spec dictates we should not modify `*p` on an error. (issue#27)
|
||||
// <http://man7.org/linux/man-pages/man3/posix_memalign.3.html>
|
||||
if (p == NULL) return EINVAL;
|
||||
if ((alignment % sizeof(void*)) != 0) return EINVAL; // natural alignment
|
||||
// it is also required that alignment is a power of 2 and > 0; this is checked in `mi_malloc_aligned`
|
||||
if (alignment==0 || !_mi_is_power_of_two(alignment)) return EINVAL; // not a power of 2
|
||||
// it is required that alignment is a power of 2 and a multiple of sizeof(void*)
|
||||
if (alignment<sizeof(void*) || !_mi_is_power_of_two(alignment)) return EINVAL; // not a power of 2
|
||||
void* q = mi_malloc_aligned(size, alignment);
|
||||
if (q==NULL && size != 0) return ENOMEM;
|
||||
mi_assert_internal(((uintptr_t)q % alignment) == 0);
|
||||
mi_assert_internal(_mi_is_aligned(q,alignment));
|
||||
*p = q;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept {
|
||||
void* p = mi_malloc_aligned(size, alignment);
|
||||
mi_assert_internal(((uintptr_t)p % alignment) == 0);
|
||||
mi_assert_internal(_mi_is_aligned(p,alignment));
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -95,27 +83,43 @@ mi_decl_nodiscard mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size
|
||||
*/
|
||||
// C11 also requires alignment to be a power-of-two (and > 0) which is checked in mi_malloc_aligned
|
||||
void* p = mi_malloc_aligned(size, alignment);
|
||||
mi_assert_internal(((uintptr_t)p % alignment) == 0);
|
||||
mi_assert_internal(_mi_is_aligned(p,alignment));
|
||||
return p;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard void* mi_reallocarray( void* p, size_t count, size_t size ) mi_attr_noexcept { // BSD
|
||||
void* newp = mi_reallocn(p,count,size);
|
||||
mi_decl_nodiscard void* mi_reallocarray( void* p, size_t count, size_t size ) mi_attr_noexcept { // BSD <https://man.freebsd.org/cgi/man.cgi?query=reallocarray>
|
||||
size_t total;
|
||||
if mi_unlikely(mi_count_size_overflow(count, size, &total)) {
|
||||
errno = EOVERFLOW;
|
||||
return NULL;
|
||||
}
|
||||
void* newp = mi_realloc(p,total);
|
||||
if (newp==NULL) { errno = ENOMEM; }
|
||||
return newp;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard int mi_reallocarr( void* p, size_t count, size_t size ) mi_attr_noexcept { // NetBSD
|
||||
mi_assert(p != NULL);
|
||||
if (p == NULL) {
|
||||
errno = EINVAL;
|
||||
return EINVAL;
|
||||
mi_decl_nodiscard int mi_reallocarr( void* ptrp, size_t count, size_t size ) mi_attr_noexcept { // NetBSD <https://man.netbsd.org/reallocarr.3>
|
||||
mi_assert(size != 0);
|
||||
mi_assert(ptrp != NULL);
|
||||
if (ptrp == NULL || size == 0) {
|
||||
return (errno = EINVAL);
|
||||
}
|
||||
size_t total;
|
||||
if mi_unlikely(mi_count_size_overflow(count, size, &total)) {
|
||||
return (errno = EOVERFLOW);
|
||||
}
|
||||
void** op = (void**)ptrp;
|
||||
if (total == 0) {
|
||||
mi_free(*op);
|
||||
*op = NULL;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
void* newp = mi_realloc(*op,total);
|
||||
if (newp == NULL) { return (errno = ENOMEM); }
|
||||
*op = newp;
|
||||
return 0;
|
||||
}
|
||||
void** op = (void**)p;
|
||||
void* newp = mi_reallocarray(*op, count, size);
|
||||
if mi_unlikely(newp == NULL) { return errno; }
|
||||
*op = newp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* mi__expand(void* p, size_t newsize) mi_attr_noexcept { // Microsoft
|
||||
@@ -124,12 +128,13 @@ void* mi__expand(void* p, size_t newsize) mi_attr_noexcept { // Microsoft
|
||||
return res;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept {
|
||||
mi_decl_nodiscard mi_decl_restrict wchar_t* mi_wcsdup(const wchar_t* s) mi_attr_noexcept {
|
||||
if (s==NULL) return NULL;
|
||||
size_t len;
|
||||
for(len = 0; s[len] != 0; len++) { }
|
||||
size_t size = (len+1)*sizeof(unsigned short);
|
||||
unsigned short* p = (unsigned short*)mi_malloc(size);
|
||||
size_t wlen;
|
||||
for(wlen = 0; s[wlen] != 0 && wlen < PTRDIFF_MAX; wlen++) { } // prevent overflow on wlen+1
|
||||
size_t size;
|
||||
if (mi_mul_overflow(wlen+1, sizeof(wchar_t), &size) || size > PTRDIFF_MAX) return NULL;
|
||||
wchar_t* p = (wchar_t*)mi_malloc(size);
|
||||
if (p != NULL) {
|
||||
_mi_memcpy(p,s,size);
|
||||
}
|
||||
@@ -141,36 +146,36 @@ mi_decl_nodiscard mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char*
|
||||
}
|
||||
|
||||
int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept {
|
||||
if (buf==NULL || name==NULL) return EINVAL;
|
||||
if (size != NULL) *size = 0;
|
||||
char* p = getenv(name); // mscver warning 4996
|
||||
if (buf==NULL || name==NULL) return EINVAL;
|
||||
char* p = getenv(name);
|
||||
if (p==NULL) {
|
||||
*buf = NULL;
|
||||
}
|
||||
else {
|
||||
*buf = mi_strdup(p);
|
||||
if (*buf==NULL) return ENOMEM;
|
||||
if (size != NULL) *size = _mi_strlen(p);
|
||||
if (size != NULL) { *size = _mi_strlen(p) + 1; } // cannot overflow as mi_strdup is limited to PTRDIFF_MAX
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept {
|
||||
if (buf==NULL || name==NULL) return EINVAL;
|
||||
int mi_wdupenv_s(wchar_t** buf, size_t* size, const wchar_t* name) mi_attr_noexcept {
|
||||
if (size != NULL) *size = 0;
|
||||
if (buf==NULL || name==NULL) return EINVAL;
|
||||
#if !defined(_WIN32) || (defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP))
|
||||
// not supported
|
||||
*buf = NULL;
|
||||
return EINVAL;
|
||||
#else
|
||||
unsigned short* p = (unsigned short*)_wgetenv((const wchar_t*)name); // msvc warning 4996
|
||||
wchar_t* p = (wchar_t*)_wgetenv(name);
|
||||
if (p==NULL) {
|
||||
*buf = NULL;
|
||||
}
|
||||
else {
|
||||
*buf = mi_wcsdup(p);
|
||||
if (*buf==NULL) return ENOMEM;
|
||||
if (size != NULL) *size = wcslen((const wchar_t*)p);
|
||||
if (size != NULL) { *size = wcslen(p) + 1; } // cannot overflow as wcsdup is limited to PTRDIFF_MAX
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
+367
-610
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,346 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2019-2024, 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.
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
#if !defined(MI_IN_ARENA_C)
|
||||
#error "this file should be included from 'arena.c' (so mi_arena_t is visible)"
|
||||
// add includes help an IDE
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "bitmap.h"
|
||||
#endif
|
||||
|
||||
// Minimal exports for arena-abandoned.
|
||||
size_t mi_arena_id_index(mi_arena_id_t id);
|
||||
mi_arena_t* mi_arena_from_index(size_t idx);
|
||||
size_t mi_arena_get_count(void);
|
||||
void* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex);
|
||||
bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index);
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Abandoned blocks/segments:
|
||||
|
||||
_mi_arena_segment_clear_abandoned
|
||||
_mi_arena_segment_mark_abandoned
|
||||
|
||||
This is used to atomically abandon/reclaim segments
|
||||
(and crosses the arena API but it is convenient to have here).
|
||||
|
||||
Abandoned segments still have live blocks; they get reclaimed
|
||||
when a thread frees a block in it, or when a thread needs a fresh
|
||||
segment.
|
||||
|
||||
Abandoned segments are atomically marked in the `block_abandoned`
|
||||
bitmap of arenas. Any segments allocated outside arenas are put
|
||||
in the sub-process `abandoned_os_list`. This list is accessed
|
||||
using locks but this should be uncommon and generally uncontended.
|
||||
Reclaim and visiting either scan through the `block_abandoned`
|
||||
bitmaps of the arena's, or visit the `abandoned_os_list`
|
||||
|
||||
A potentially nicer design is to use arena's for everything
|
||||
and perhaps have virtual arena's to map OS allocated memory
|
||||
but this would lack the "density" of our current arena's. TBC.
|
||||
----------------------------------------------------------- */
|
||||
|
||||
|
||||
// reclaim a specific OS abandoned segment; `true` on success.
|
||||
// sets the thread_id.
|
||||
static bool mi_arena_segment_os_clear_abandoned(mi_segment_t* segment, bool take_lock) {
|
||||
mi_assert(segment->memid.memkind != MI_MEM_ARENA);
|
||||
// not in an arena, remove from list of abandoned os segments
|
||||
mi_subproc_t* const subproc = segment->subproc;
|
||||
if (take_lock && !mi_lock_try_acquire(&subproc->abandoned_os_lock)) {
|
||||
return false; // failed to acquire the lock, we just give up
|
||||
}
|
||||
// remove atomically from the abandoned os list (if possible!)
|
||||
bool reclaimed = false;
|
||||
mi_segment_t* const next = segment->abandoned_os_next;
|
||||
mi_segment_t* const prev = segment->abandoned_os_prev;
|
||||
if (next != NULL || prev != NULL || subproc->abandoned_os_list == segment) {
|
||||
#if MI_DEBUG>3
|
||||
// find ourselves in the abandoned list (and check the count)
|
||||
bool found = false;
|
||||
size_t count = 0;
|
||||
for (mi_segment_t* current = subproc->abandoned_os_list; current != NULL; current = current->abandoned_os_next) {
|
||||
if (current == segment) { found = true; }
|
||||
count++;
|
||||
}
|
||||
mi_assert_internal(found);
|
||||
mi_assert_internal(count == mi_atomic_load_relaxed(&subproc->abandoned_os_list_count));
|
||||
#endif
|
||||
// remove (atomically) from the list and reclaim
|
||||
if (prev != NULL) { prev->abandoned_os_next = next; }
|
||||
else { subproc->abandoned_os_list = next; }
|
||||
if (next != NULL) { next->abandoned_os_prev = prev; }
|
||||
else { subproc->abandoned_os_list_tail = prev; }
|
||||
segment->abandoned_os_next = NULL;
|
||||
segment->abandoned_os_prev = NULL;
|
||||
mi_atomic_decrement_relaxed(&subproc->abandoned_count);
|
||||
mi_atomic_decrement_relaxed(&subproc->abandoned_os_list_count);
|
||||
if (take_lock) { // don't reset the thread_id when iterating
|
||||
mi_atomic_store_release(&segment->thread_id, _mi_thread_id());
|
||||
}
|
||||
reclaimed = true;
|
||||
}
|
||||
if (take_lock) { mi_lock_release(&segment->subproc->abandoned_os_lock); }
|
||||
return reclaimed;
|
||||
}
|
||||
|
||||
// reclaim a specific abandoned segment; `true` on success.
|
||||
// sets the thread_id.
|
||||
bool _mi_arena_segment_clear_abandoned(mi_segment_t* segment) {
|
||||
if mi_unlikely(segment->memid.memkind != MI_MEM_ARENA) {
|
||||
return mi_arena_segment_os_clear_abandoned(segment, true /* take lock */);
|
||||
}
|
||||
// arena segment: use the blocks_abandoned bitmap.
|
||||
size_t arena_idx;
|
||||
size_t bitmap_idx;
|
||||
mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);
|
||||
mi_arena_t* arena = mi_arena_from_index(arena_idx);
|
||||
mi_assert_internal(arena != NULL);
|
||||
// reclaim atomically
|
||||
bool was_marked = _mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx);
|
||||
if (was_marked) {
|
||||
mi_assert_internal(mi_atomic_load_acquire(&segment->thread_id) == 0);
|
||||
mi_atomic_decrement_relaxed(&segment->subproc->abandoned_count);
|
||||
mi_atomic_store_release(&segment->thread_id, _mi_thread_id());
|
||||
}
|
||||
// mi_assert_internal(was_marked);
|
||||
mi_assert_internal(!was_marked || _mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
|
||||
return was_marked;
|
||||
}
|
||||
|
||||
|
||||
// mark a specific OS segment as abandoned
|
||||
static void mi_arena_segment_os_mark_abandoned(mi_segment_t* segment) {
|
||||
mi_assert(segment->memid.memkind != MI_MEM_ARENA);
|
||||
// not in an arena; we use a list of abandoned segments
|
||||
mi_subproc_t* const subproc = segment->subproc;
|
||||
mi_lock(&subproc->abandoned_os_lock) {
|
||||
// push on the tail of the list (important for the visitor)
|
||||
mi_segment_t* prev = subproc->abandoned_os_list_tail;
|
||||
mi_assert_internal(prev == NULL || prev->abandoned_os_next == NULL);
|
||||
mi_assert_internal(segment->abandoned_os_prev == NULL);
|
||||
mi_assert_internal(segment->abandoned_os_next == NULL);
|
||||
if (prev != NULL) { prev->abandoned_os_next = segment; }
|
||||
else { subproc->abandoned_os_list = segment; }
|
||||
subproc->abandoned_os_list_tail = segment;
|
||||
segment->abandoned_os_prev = prev;
|
||||
segment->abandoned_os_next = NULL;
|
||||
mi_atomic_increment_relaxed(&subproc->abandoned_os_list_count);
|
||||
mi_atomic_increment_relaxed(&subproc->abandoned_count);
|
||||
// and release the lock
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// mark a specific segment as abandoned
|
||||
// clears the thread_id.
|
||||
void _mi_arena_segment_mark_abandoned(mi_segment_t* segment)
|
||||
{
|
||||
mi_assert_internal(segment->used == segment->abandoned);
|
||||
mi_atomic_store_release(&segment->thread_id, (uintptr_t)0); // mark as abandoned for multi-thread free's
|
||||
if mi_unlikely(segment->memid.memkind != MI_MEM_ARENA) {
|
||||
mi_arena_segment_os_mark_abandoned(segment);
|
||||
return;
|
||||
}
|
||||
// segment is in an arena, mark it in the arena `blocks_abandoned` bitmap
|
||||
size_t arena_idx;
|
||||
size_t bitmap_idx;
|
||||
mi_arena_memid_indices(segment->memid, &arena_idx, &bitmap_idx);
|
||||
mi_arena_t* arena = mi_arena_from_index(arena_idx);
|
||||
mi_assert_internal(arena != NULL);
|
||||
// set abandonment atomically
|
||||
mi_subproc_t* const subproc = segment->subproc; // don't access the segment after setting it abandoned
|
||||
const bool was_unmarked = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);
|
||||
if (was_unmarked) { mi_atomic_increment_relaxed(&subproc->abandoned_count); }
|
||||
mi_assert_internal(was_unmarked);
|
||||
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Iterate through the abandoned blocks/segments using a cursor.
|
||||
This is used for reclaiming and abandoned block visiting.
|
||||
----------------------------------------------------------- */
|
||||
|
||||
// start a cursor at a randomized arena
|
||||
void _mi_arena_field_cursor_init(mi_heap_t* heap, mi_subproc_t* subproc, bool visit_all, mi_arena_field_cursor_t* current) {
|
||||
mi_assert_internal(heap == NULL || heap->tld->segments.subproc == subproc);
|
||||
current->bitmap_idx = 0;
|
||||
current->subproc = subproc;
|
||||
current->visit_all = visit_all;
|
||||
current->hold_visit_lock = false;
|
||||
const size_t abandoned_count = mi_atomic_load_relaxed(&subproc->abandoned_count);
|
||||
const size_t abandoned_list_count = mi_atomic_load_relaxed(&subproc->abandoned_os_list_count);
|
||||
const size_t max_arena = mi_arena_get_count();
|
||||
if (heap != NULL && heap->arena_id != _mi_arena_id_none()) {
|
||||
// for a heap that is bound to one arena, only visit that arena
|
||||
current->start = mi_arena_id_index(heap->arena_id);
|
||||
current->end = current->start + 1;
|
||||
current->os_list_count = 0;
|
||||
}
|
||||
else {
|
||||
// otherwise visit all starting at a random location
|
||||
if (abandoned_count > abandoned_list_count && max_arena > 0) {
|
||||
current->start = (heap == NULL || max_arena == 0 ? 0 : (mi_arena_id_t)(_mi_heap_random_next(heap) % max_arena));
|
||||
current->end = current->start + max_arena;
|
||||
}
|
||||
else {
|
||||
current->start = 0;
|
||||
current->end = 0;
|
||||
}
|
||||
current->os_list_count = abandoned_list_count; // max entries to visit in the os abandoned list
|
||||
}
|
||||
mi_assert_internal(current->start <= max_arena);
|
||||
}
|
||||
|
||||
void _mi_arena_field_cursor_done(mi_arena_field_cursor_t* current) {
|
||||
if (current->hold_visit_lock) {
|
||||
mi_lock_release(¤t->subproc->abandoned_os_visit_lock);
|
||||
current->hold_visit_lock = false;
|
||||
}
|
||||
}
|
||||
|
||||
static mi_segment_t* mi_arena_segment_clear_abandoned_at(mi_arena_t* arena, mi_subproc_t* subproc, mi_bitmap_index_t bitmap_idx) {
|
||||
// try to reclaim an abandoned segment in the arena atomically
|
||||
if (!_mi_bitmap_unclaim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx)) return NULL;
|
||||
mi_assert_internal(_mi_bitmap_is_claimed(arena->blocks_inuse, arena->field_count, 1, bitmap_idx));
|
||||
mi_segment_t* segment = (mi_segment_t*)mi_arena_block_start(arena, bitmap_idx);
|
||||
mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == 0);
|
||||
// check that the segment belongs to our sub-process
|
||||
// note: this is the reason we need the `abandoned_visit` lock in the case abandoned visiting is enabled.
|
||||
// without the lock an abandoned visit may otherwise fail to visit all abandoned segments in the sub-process.
|
||||
// for regular reclaim it is fine to miss one sometimes so without abandoned visiting we don't need the `abandoned_visit` lock.
|
||||
if (segment->subproc != subproc) {
|
||||
// it is from another sub-process, re-mark it and continue searching
|
||||
const bool was_zero = _mi_bitmap_claim(arena->blocks_abandoned, arena->field_count, 1, bitmap_idx, NULL);
|
||||
mi_assert_internal(was_zero); MI_UNUSED(was_zero);
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
// success, we unabandoned a segment in our sub-process
|
||||
mi_atomic_decrement_relaxed(&subproc->abandoned_count);
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
static mi_segment_t* mi_arena_segment_clear_abandoned_next_field(mi_arena_field_cursor_t* previous) {
|
||||
const size_t max_arena = mi_arena_get_count();
|
||||
size_t field_idx = mi_bitmap_index_field(previous->bitmap_idx);
|
||||
size_t bit_idx = mi_bitmap_index_bit_in_field(previous->bitmap_idx);
|
||||
// visit arena's (from the previous cursor)
|
||||
for (; previous->start < previous->end; previous->start++, field_idx = 0, bit_idx = 0) {
|
||||
// index wraps around
|
||||
size_t arena_idx = (previous->start >= max_arena ? previous->start % max_arena : previous->start);
|
||||
mi_arena_t* arena = mi_arena_from_index(arena_idx);
|
||||
if (arena != NULL) {
|
||||
bool has_lock = false;
|
||||
// visit the abandoned fields (starting at previous_idx)
|
||||
for (; field_idx < arena->field_count; field_idx++, bit_idx = 0) {
|
||||
size_t field = mi_atomic_load_relaxed(&arena->blocks_abandoned[field_idx]);
|
||||
if mi_unlikely(field != 0) { // skip zero fields quickly
|
||||
// we only take the arena lock if there are actually abandoned segments present
|
||||
if (!has_lock && mi_option_is_enabled(mi_option_visit_abandoned)) {
|
||||
has_lock = (previous->visit_all ? (mi_lock_acquire(&arena->abandoned_visit_lock),true) : mi_lock_try_acquire(&arena->abandoned_visit_lock));
|
||||
if (!has_lock) {
|
||||
if (previous->visit_all) {
|
||||
_mi_error_message(EFAULT, "internal error: failed to visit all abandoned segments due to failure to acquire the visitor lock");
|
||||
}
|
||||
// skip to next arena
|
||||
break;
|
||||
}
|
||||
}
|
||||
mi_assert_internal(has_lock || !mi_option_is_enabled(mi_option_visit_abandoned));
|
||||
// visit each set bit in the field (todo: maybe use `ctz` here?)
|
||||
for (; bit_idx < MI_BITMAP_FIELD_BITS; bit_idx++) {
|
||||
// pre-check if the bit is set
|
||||
size_t mask = ((size_t)1 << bit_idx);
|
||||
if mi_unlikely((field & mask) == mask) {
|
||||
mi_bitmap_index_t bitmap_idx = mi_bitmap_index_create(field_idx, bit_idx);
|
||||
mi_segment_t* const segment = mi_arena_segment_clear_abandoned_at(arena, previous->subproc, bitmap_idx);
|
||||
if (segment != NULL) {
|
||||
//mi_assert_internal(arena->blocks_committed == NULL || _mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, 1, bitmap_idx));
|
||||
if (has_lock) { mi_lock_release(&arena->abandoned_visit_lock); }
|
||||
previous->bitmap_idx = mi_bitmap_index_create_ex(field_idx, bit_idx + 1); // start at next one for the next iteration
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_lock) { mi_lock_release(&arena->abandoned_visit_lock); }
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static mi_segment_t* mi_arena_segment_clear_abandoned_next_list(mi_arena_field_cursor_t* previous) {
|
||||
// go through the abandoned_os_list
|
||||
// we only allow one thread per sub-process to do to visit guarded by the `abandoned_os_visit_lock`.
|
||||
// The lock is released when the cursor is released.
|
||||
if (!previous->hold_visit_lock) {
|
||||
previous->hold_visit_lock = (previous->visit_all ? (mi_lock_acquire(&previous->subproc->abandoned_os_visit_lock),true)
|
||||
: mi_lock_try_acquire(&previous->subproc->abandoned_os_visit_lock));
|
||||
if (!previous->hold_visit_lock) {
|
||||
if (previous->visit_all) {
|
||||
_mi_error_message(EFAULT, "internal error: failed to visit all abandoned segments due to failure to acquire the OS visitor lock");
|
||||
}
|
||||
return NULL; // we cannot get the lock, give up
|
||||
}
|
||||
}
|
||||
// One list entry at a time
|
||||
while (previous->os_list_count > 0) {
|
||||
previous->os_list_count--;
|
||||
mi_lock_acquire(&previous->subproc->abandoned_os_lock); // this could contend with concurrent OS block abandonment and reclaim from `free`
|
||||
mi_segment_t* segment = previous->subproc->abandoned_os_list;
|
||||
// pop from head of the list, a subsequent mark will push at the end (and thus we iterate through os_list_count entries)
|
||||
if (segment == NULL || mi_arena_segment_os_clear_abandoned(segment, false /* we already have the lock */)) {
|
||||
mi_lock_release(&previous->subproc->abandoned_os_lock);
|
||||
return segment;
|
||||
}
|
||||
// already abandoned, try again
|
||||
mi_lock_release(&previous->subproc->abandoned_os_lock);
|
||||
}
|
||||
// done
|
||||
mi_assert_internal(previous->os_list_count == 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// reclaim abandoned segments
|
||||
// this does not set the thread id (so it appears as still abandoned)
|
||||
mi_segment_t* _mi_arena_segment_clear_abandoned_next(mi_arena_field_cursor_t* previous) {
|
||||
if (previous->start < previous->end) {
|
||||
// walk the arena
|
||||
mi_segment_t* segment = mi_arena_segment_clear_abandoned_next_field(previous);
|
||||
if (segment != NULL) { return segment; }
|
||||
}
|
||||
// no entries in the arena's anymore, walk the abandoned OS list
|
||||
mi_assert_internal(previous->start == previous->end);
|
||||
return mi_arena_segment_clear_abandoned_next_list(previous);
|
||||
}
|
||||
|
||||
|
||||
bool mi_abandoned_visit_blocks(mi_subproc_id_t subproc_id, int heap_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {
|
||||
// (unfortunately) the visit_abandoned option must be enabled from the start.
|
||||
// This is to avoid taking locks if abandoned list visiting is not required (as for most programs)
|
||||
if (!mi_option_is_enabled(mi_option_visit_abandoned)) {
|
||||
_mi_error_message(EFAULT, "internal error: can only visit abandoned blocks when MIMALLOC_VISIT_ABANDONED=ON");
|
||||
return false;
|
||||
}
|
||||
mi_arena_field_cursor_t current;
|
||||
_mi_arena_field_cursor_init(NULL, _mi_subproc_from_id(subproc_id), true /* visit all (blocking) */, ¤t);
|
||||
mi_segment_t* segment;
|
||||
bool ok = true;
|
||||
while (ok && (segment = _mi_arena_segment_clear_abandoned_next(¤t)) != NULL) {
|
||||
ok = _mi_segment_visit_blocks(segment, heap_tag, visit_blocks, visitor, arg);
|
||||
_mi_arena_segment_mark_abandoned(segment);
|
||||
}
|
||||
_mi_arena_field_cursor_done(¤t);
|
||||
return ok;
|
||||
}
|
||||
+314
-181
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2019-2023, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2019-2024, 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.
|
||||
@@ -11,67 +11,68 @@ large blocks (>= MI_ARENA_MIN_BLOCK_SIZE, 4MiB).
|
||||
In contrast to the rest of mimalloc, the arenas are shared between
|
||||
threads and need to be accessed using atomic operations.
|
||||
|
||||
Arenas are used to for huge OS page (1GiB) reservations or for reserving
|
||||
Arenas are also used to for huge OS page (1GiB) reservations or for reserving
|
||||
OS memory upfront which can be improve performance or is sometimes needed
|
||||
on embedded devices. We can also employ this with WASI or `sbrk` systems
|
||||
to reserve large arenas upfront and be able to reuse the memory more effectively.
|
||||
|
||||
The arena allocation needs to be thread safe and we use an atomic bitmap to allocate.
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "bitmap.h"
|
||||
|
||||
#include <string.h> // memset
|
||||
#include <errno.h> // ENOMEM
|
||||
|
||||
#include "bitmap.h" // atomic bitmap
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Arena allocation
|
||||
----------------------------------------------------------- */
|
||||
|
||||
// Block info: bit 0 contains the `in_use` bit, the upper bits the
|
||||
// size in count of arena blocks.
|
||||
typedef uintptr_t mi_block_info_t;
|
||||
#define MI_ARENA_BLOCK_SIZE (MI_SEGMENT_SIZE) // 64MiB (must be at least MI_SEGMENT_ALIGN)
|
||||
#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 32MiB
|
||||
#define MI_MAX_ARENAS (112) // not more than 126 (since we use 7 bits in the memid and an arena index + 1)
|
||||
|
||||
// A memory arena descriptor
|
||||
typedef struct mi_arena_s {
|
||||
mi_arena_id_t id; // arena id; 0 for non-specific
|
||||
mi_memid_t memid; // memid of the memory area
|
||||
_Atomic(uint8_t*) start; // the start of the memory area
|
||||
size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`)
|
||||
size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`)
|
||||
size_t meta_size; // size of the arena structure itself (including its bitmaps)
|
||||
mi_memid_t meta_memid; // memid of the arena structure itself (OS or static allocation)
|
||||
int numa_node; // associated NUMA node
|
||||
bool exclusive; // only allow allocations if specifically for this arena
|
||||
bool is_large; // memory area consists of large- or huge OS pages (always committed)
|
||||
_Atomic(size_t) search_idx; // optimization to start the search for free blocks
|
||||
_Atomic(mi_msecs_t) purge_expire; // expiration time when blocks should be decommitted from `blocks_decommit`.
|
||||
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
|
||||
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
|
||||
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
|
||||
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
|
||||
mi_arena_id_t id; // arena id; 0 for non-specific
|
||||
mi_memid_t memid; // memid of the memory area
|
||||
_Atomic(uint8_t*) start; // the start of the memory area
|
||||
size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`)
|
||||
size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`)
|
||||
size_t meta_size; // size of the arena structure itself (including its bitmaps)
|
||||
mi_memid_t meta_memid; // memid of the arena structure itself (OS or static allocation)
|
||||
int numa_node; // associated NUMA node
|
||||
bool exclusive; // only allow allocations if specifically for this arena
|
||||
bool is_large; // memory area consists of large- or huge OS pages (always committed)
|
||||
mi_lock_t abandoned_visit_lock; // lock is only used when abandoned segments are being visited
|
||||
_Atomic(size_t) search_idx; // optimization to start the search for free blocks
|
||||
_Atomic(mi_msecs_t) purge_expire; // expiration time when blocks should be purged from `blocks_purge`.
|
||||
|
||||
mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero?
|
||||
mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted)
|
||||
mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted)
|
||||
mi_bitmap_field_t* blocks_abandoned; // blocks that start with an abandoned segment. (This crosses API's but it is convenient to have here)
|
||||
mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`)
|
||||
// do not add further fields here as the dirty, committed, purged, and abandoned bitmaps follow the inuse bitmap fields.
|
||||
} mi_arena_t;
|
||||
|
||||
|
||||
#define MI_ARENA_BLOCK_SIZE (MI_SEGMENT_SIZE) // 64MiB (must be at least MI_SEGMENT_ALIGN)
|
||||
#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 32MiB
|
||||
#define MI_MAX_ARENAS (132) // Limited as the reservation exponentially increases (and takes up .bss)
|
||||
|
||||
// The available arenas
|
||||
static mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS];
|
||||
static mi_decl_cache_align _Atomic(size_t) mi_arena_count; // = 0
|
||||
static mi_decl_cache_align _Atomic(int64_t) mi_arenas_purge_expire; // set if there exist purgeable arenas
|
||||
|
||||
|
||||
//static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept;
|
||||
#define MI_IN_ARENA_C
|
||||
#include "arena-abandon.c"
|
||||
#undef MI_IN_ARENA_C
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Arena id's
|
||||
id = arena_index + 1
|
||||
----------------------------------------------------------- */
|
||||
|
||||
static size_t mi_arena_id_index(mi_arena_id_t id) {
|
||||
size_t mi_arena_id_index(mi_arena_id_t id) {
|
||||
return (size_t)(id <= 0 ? MI_MAX_ARENAS : id - 1);
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_i
|
||||
return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id);
|
||||
}
|
||||
else {
|
||||
return mi_arena_id_is_suitable(0, false, request_arena_id);
|
||||
return mi_arena_id_is_suitable(_mi_arena_id_none(), false, request_arena_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +103,16 @@ bool _mi_arena_memid_is_os_allocated(mi_memid_t memid) {
|
||||
return (memid.memkind == MI_MEM_OS);
|
||||
}
|
||||
|
||||
size_t mi_arena_get_count(void) {
|
||||
return mi_atomic_load_relaxed(&mi_arena_count);
|
||||
}
|
||||
|
||||
mi_arena_t* mi_arena_from_index(size_t idx) {
|
||||
mi_assert_internal(idx < mi_arena_get_count());
|
||||
return mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[idx]);
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Arena allocations get a (currently) 16-bit memory id where the
|
||||
lower 8 bits are the arena id, and the upper bits the block index.
|
||||
@@ -127,7 +138,7 @@ static mi_memid_t mi_memid_create_arena(mi_arena_id_t id, bool is_exclusive, mi_
|
||||
return memid;
|
||||
}
|
||||
|
||||
static bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) {
|
||||
bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) {
|
||||
mi_assert_internal(memid.memkind == MI_MEM_ARENA);
|
||||
*arena_index = mi_arena_id_index(memid.mem.arena.id);
|
||||
*bitmap_index = memid.mem.arena.block_index;
|
||||
@@ -138,23 +149,24 @@ static bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bit
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Special static area for mimalloc internal structures
|
||||
to avoid OS calls (for example, for the arena metadata)
|
||||
to avoid OS calls (for example, for the arena metadata (~= 256b))
|
||||
----------------------------------------------------------- */
|
||||
|
||||
#define MI_ARENA_STATIC_MAX (MI_INTPTR_SIZE*MI_KiB) // 8 KiB on 64-bit
|
||||
#define MI_ARENA_STATIC_MAX ((MI_INTPTR_SIZE/2)*MI_KiB) // 4 KiB on 64-bit
|
||||
|
||||
static uint8_t mi_arena_static[MI_ARENA_STATIC_MAX];
|
||||
static _Atomic(size_t) mi_arena_static_top;
|
||||
static mi_decl_cache_align uint8_t mi_arena_static[MI_ARENA_STATIC_MAX]; // must be cache aligned, see issue #895
|
||||
static mi_decl_cache_align _Atomic(size_t) mi_arena_static_top;
|
||||
|
||||
static void* mi_arena_static_zalloc(size_t size, size_t alignment, mi_memid_t* memid) {
|
||||
*memid = _mi_memid_none();
|
||||
if (size == 0 || size > MI_ARENA_STATIC_MAX) return NULL;
|
||||
if ((mi_atomic_load_relaxed(&mi_arena_static_top) + size) > MI_ARENA_STATIC_MAX) return NULL;
|
||||
const size_t toplow = mi_atomic_load_relaxed(&mi_arena_static_top);
|
||||
if ((toplow + size) > MI_ARENA_STATIC_MAX) return NULL;
|
||||
|
||||
// try to claim space
|
||||
if (alignment == 0) { alignment = 1; }
|
||||
if (alignment < MI_MAX_ALIGN_SIZE) { alignment = MI_MAX_ALIGN_SIZE; }
|
||||
const size_t oversize = size + alignment - 1;
|
||||
if (oversize > MI_ARENA_STATIC_MAX) return NULL;
|
||||
if (toplow + oversize > MI_ARENA_STATIC_MAX) return NULL;
|
||||
const size_t oldtop = mi_atomic_add_acq_rel(&mi_arena_static_top, oversize);
|
||||
size_t top = oldtop + oversize;
|
||||
if (top > MI_ARENA_STATIC_MAX) {
|
||||
@@ -165,33 +177,37 @@ static void* mi_arena_static_zalloc(size_t size, size_t alignment, mi_memid_t* m
|
||||
|
||||
// success
|
||||
*memid = _mi_memid_create(MI_MEM_STATIC);
|
||||
memid->initially_zero = true;
|
||||
const size_t start = _mi_align_up(oldtop, alignment);
|
||||
uint8_t* const p = &mi_arena_static[start];
|
||||
_mi_memzero(p, size);
|
||||
_mi_memzero_aligned(p, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* mi_arena_meta_zalloc(size_t size, mi_memid_t* memid, mi_stats_t* stats) {
|
||||
void* _mi_arena_meta_zalloc(size_t size, mi_memid_t* memid) {
|
||||
*memid = _mi_memid_none();
|
||||
|
||||
// try static
|
||||
void* p = mi_arena_static_zalloc(size, MI_ALIGNMENT_MAX, memid);
|
||||
void* p = mi_arena_static_zalloc(size, MI_MAX_ALIGN_SIZE, memid);
|
||||
if (p != NULL) return p;
|
||||
|
||||
// or fall back to the OS
|
||||
return _mi_os_alloc(size, memid, stats);
|
||||
p = _mi_os_zalloc(size, memid);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void mi_arena_meta_free(void* p, mi_memid_t memid, size_t size, mi_stats_t* stats) {
|
||||
void _mi_arena_meta_free(void* p, mi_memid_t memid, size_t size) {
|
||||
if (mi_memkind_is_os(memid.memkind)) {
|
||||
_mi_os_free(p, size, memid, stats);
|
||||
_mi_os_free(p, size, memid);
|
||||
}
|
||||
else {
|
||||
mi_assert(memid.memkind == MI_MEM_STATIC);
|
||||
}
|
||||
}
|
||||
|
||||
static void* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex) {
|
||||
void* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex) {
|
||||
return (arena->start + mi_arena_block_size(mi_bitmap_index_bit(bindex)));
|
||||
}
|
||||
|
||||
@@ -217,7 +233,7 @@ static bool mi_arena_try_claim(mi_arena_t* arena, size_t blocks, mi_bitmap_index
|
||||
----------------------------------------------------------- */
|
||||
|
||||
static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t arena_index, size_t needed_bcount,
|
||||
bool commit, mi_memid_t* memid, mi_os_tld_t* tld)
|
||||
bool commit, mi_memid_t* memid)
|
||||
{
|
||||
MI_UNUSED(arena_index);
|
||||
mi_assert_internal(mi_arena_id_index(arena->id) == arena_index);
|
||||
@@ -238,7 +254,7 @@ static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t ar
|
||||
|
||||
// set the dirty bits (todo: no need for an atomic op here?)
|
||||
if (arena->memid.initially_zero && arena->blocks_dirty != NULL) {
|
||||
memid->initially_zero = _mi_bitmap_claim_across(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL);
|
||||
memid->initially_zero = _mi_bitmap_claim_across(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL, NULL);
|
||||
}
|
||||
|
||||
// set commit state
|
||||
@@ -249,21 +265,38 @@ static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t ar
|
||||
else if (commit) {
|
||||
// commit requested, but the range may not be committed as a whole: ensure it is committed now
|
||||
memid->initially_committed = true;
|
||||
const size_t commit_size = mi_arena_block_size(needed_bcount);
|
||||
bool any_uncommitted;
|
||||
_mi_bitmap_claim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted);
|
||||
size_t already_committed = 0;
|
||||
_mi_bitmap_claim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted, &already_committed);
|
||||
if (any_uncommitted) {
|
||||
mi_assert_internal(already_committed < needed_bcount);
|
||||
const size_t stat_commit_size = commit_size - mi_arena_block_size(already_committed);
|
||||
bool commit_zero = false;
|
||||
if (!_mi_os_commit(p, mi_arena_block_size(needed_bcount), &commit_zero, tld->stats)) {
|
||||
if (!_mi_os_commit_ex(p, commit_size, &commit_zero, stat_commit_size)) {
|
||||
// set all as uncommitted on commit failure
|
||||
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index);
|
||||
memid->initially_committed = false;
|
||||
}
|
||||
else {
|
||||
if (commit_zero) { memid->initially_zero = true; }
|
||||
}
|
||||
}
|
||||
else {
|
||||
// all are already committed: signal that we are reusing memory in case it was purged before
|
||||
_mi_os_reuse( p, commit_size );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// no need to commit, but check if already fully committed
|
||||
memid->initially_committed = _mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index);
|
||||
size_t already_committed = 0;
|
||||
memid->initially_committed = _mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &already_committed);
|
||||
if (!memid->initially_committed && already_committed > 0) {
|
||||
// partially committed: as it will be committed at some time, adjust the stats and pretend the range is fully uncommitted.
|
||||
mi_assert_internal(already_committed < needed_bcount);
|
||||
_mi_stat_decrease(&_mi_stats_main.committed, mi_arena_block_size(already_committed));
|
||||
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
@@ -271,17 +304,17 @@ static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t ar
|
||||
|
||||
// allocate in a specific arena
|
||||
static void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_node, int numa_node, size_t size, size_t alignment,
|
||||
bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld )
|
||||
bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid )
|
||||
{
|
||||
MI_UNUSED_RELEASE(alignment);
|
||||
mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);
|
||||
mi_assert(alignment <= MI_SEGMENT_ALIGN);
|
||||
const size_t bcount = mi_block_count_of_size(size);
|
||||
const size_t arena_index = mi_arena_id_index(arena_id);
|
||||
mi_assert_internal(arena_index < mi_atomic_load_relaxed(&mi_arena_count));
|
||||
mi_assert_internal(size <= mi_arena_block_size(bcount));
|
||||
|
||||
// Check arena suitability
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]);
|
||||
mi_arena_t* arena = mi_arena_from_index(arena_index);
|
||||
if (arena == NULL) return NULL;
|
||||
if (!allow_large && arena->is_large) return NULL;
|
||||
if (!mi_arena_id_is_suitable(arena->id, arena->exclusive, req_arena_id)) return NULL;
|
||||
@@ -292,7 +325,7 @@ static void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_no
|
||||
}
|
||||
|
||||
// try to allocate
|
||||
void* p = mi_arena_try_alloc_at(arena, arena_index, bcount, commit, memid, tld);
|
||||
void* p = mi_arena_try_alloc_at(arena, arena_index, bcount, commit, memid);
|
||||
mi_assert_internal(p == NULL || _mi_is_aligned(p, alignment));
|
||||
return p;
|
||||
}
|
||||
@@ -301,7 +334,7 @@ static void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_no
|
||||
// allocate from an arena with fallback to the OS
|
||||
static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, size_t alignment,
|
||||
bool commit, bool allow_large,
|
||||
mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld )
|
||||
mi_arena_id_t req_arena_id, mi_memid_t* memid )
|
||||
{
|
||||
MI_UNUSED(alignment);
|
||||
mi_assert_internal(alignment <= MI_SEGMENT_ALIGN);
|
||||
@@ -311,21 +344,21 @@ static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, siz
|
||||
if (req_arena_id != _mi_arena_id_none()) {
|
||||
// try a specific arena if requested
|
||||
if (mi_arena_id_index(req_arena_id) < max_arena) {
|
||||
void* p = mi_arena_try_alloc_at_id(req_arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
|
||||
void* p = mi_arena_try_alloc_at_id(req_arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
|
||||
if (p != NULL) return p;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// try numa affine allocation
|
||||
for (size_t i = 0; i < max_arena; i++) {
|
||||
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
|
||||
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
|
||||
if (p != NULL) return p;
|
||||
}
|
||||
|
||||
// try from another numa node instead..
|
||||
if (numa_node >= 0) { // if numa_node was < 0 (no specific affinity requested), all arena's have been tried already
|
||||
for (size_t i = 0; i < max_arena; i++) {
|
||||
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), false /* only proceed if not numa local */, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
|
||||
void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), false /* only proceed if not numa local */, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
|
||||
if (p != NULL) return p;
|
||||
}
|
||||
}
|
||||
@@ -334,10 +367,9 @@ static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, siz
|
||||
}
|
||||
|
||||
// try to reserve a fresh arena space
|
||||
static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t req_arena_id, mi_arena_id_t *arena_id)
|
||||
static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t *arena_id)
|
||||
{
|
||||
if (_mi_preloading()) return false; // use OS only while pre loading
|
||||
if (req_arena_id != _mi_arena_id_none()) return false;
|
||||
|
||||
const size_t arena_count = mi_atomic_load_acquire(&mi_arena_count);
|
||||
if (arena_count > (MI_MAX_ARENAS - 4)) return false;
|
||||
@@ -346,67 +378,76 @@ static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t re
|
||||
if (arena_reserve == 0) return false;
|
||||
|
||||
if (!_mi_os_has_virtual_reserve()) {
|
||||
arena_reserve = arena_reserve/4; // be conservative if virtual reserve is not supported (for some embedded systems for example)
|
||||
arena_reserve = arena_reserve/4; // be conservative if virtual reserve is not supported (for WASM for example)
|
||||
}
|
||||
arena_reserve = _mi_align_up(arena_reserve, MI_ARENA_BLOCK_SIZE);
|
||||
arena_reserve = _mi_align_up(arena_reserve, MI_SEGMENT_SIZE);
|
||||
if (arena_count >= 8 && arena_count <= 128) {
|
||||
arena_reserve = ((size_t)1<<(arena_count/8)) * arena_reserve; // scale up the arena sizes exponentially
|
||||
// scale up the arena sizes exponentially every 8 entries (128 entries get to 589TiB)
|
||||
const size_t multiplier = (size_t)1 << _mi_clamp(arena_count/8, 0, 16 );
|
||||
size_t reserve = 0;
|
||||
if (!mi_mul_overflow(multiplier, arena_reserve, &reserve)) {
|
||||
arena_reserve = reserve;
|
||||
}
|
||||
}
|
||||
if (arena_reserve < req_size) return false; // should be able to at least handle the current allocation size
|
||||
|
||||
// commit eagerly?
|
||||
bool arena_commit = false;
|
||||
if (mi_option_get(mi_option_arena_eager_commit) == 2) { arena_commit = _mi_os_has_overcommit(); }
|
||||
if (mi_option_get(mi_option_arena_eager_commit) == 2) { arena_commit = _mi_os_has_overcommit() || mi_option_is_enabled(mi_option_allow_large_os_pages); }
|
||||
else if (mi_option_get(mi_option_arena_eager_commit) == 1) { arena_commit = true; }
|
||||
|
||||
return (mi_reserve_os_memory_ex(arena_reserve, arena_commit, allow_large, false /* exclusive */, arena_id) == 0);
|
||||
return (mi_reserve_os_memory_ex(arena_reserve, arena_commit, allow_large, false /* exclusive? */, arena_id) == 0);
|
||||
}
|
||||
|
||||
|
||||
void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large,
|
||||
mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld)
|
||||
mi_arena_id_t req_arena_id, mi_memid_t* memid)
|
||||
{
|
||||
mi_assert_internal(memid != NULL && tld != NULL);
|
||||
mi_assert_internal(memid != NULL);
|
||||
mi_assert_internal(size > 0);
|
||||
*memid = _mi_memid_none();
|
||||
|
||||
const int numa_node = _mi_os_numa_node(tld); // current numa node
|
||||
const int numa_node = _mi_os_numa_node(); // current numa node
|
||||
|
||||
// try to allocate in an arena if the alignment is small enough and the object is not too small (as for heap meta data)
|
||||
if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN && align_offset == 0) {
|
||||
void* p = mi_arena_try_alloc(numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
|
||||
if (p != NULL) return p;
|
||||
if (!mi_option_is_enabled(mi_option_disallow_arena_alloc)) { // is arena allocation allowed?
|
||||
if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN && align_offset == 0)
|
||||
{
|
||||
void* p = mi_arena_try_alloc(numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
|
||||
if (p != NULL) return p;
|
||||
|
||||
// otherwise, try to first eagerly reserve a new arena
|
||||
if (req_arena_id == _mi_arena_id_none()) {
|
||||
mi_arena_id_t arena_id = 0;
|
||||
if (mi_arena_reserve(size, allow_large, req_arena_id, &arena_id)) {
|
||||
// and try allocate in there
|
||||
mi_assert_internal(req_arena_id == _mi_arena_id_none());
|
||||
p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld);
|
||||
if (p != NULL) return p;
|
||||
// otherwise, try to first eagerly reserve a new arena
|
||||
if (req_arena_id == _mi_arena_id_none()) {
|
||||
mi_arena_id_t arena_id = 0;
|
||||
if (mi_arena_reserve(size, allow_large, &arena_id)) {
|
||||
// and try allocate in there
|
||||
mi_assert_internal(req_arena_id == _mi_arena_id_none());
|
||||
p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid);
|
||||
if (p != NULL) return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we cannot use OS allocation, return NULL
|
||||
if (mi_option_is_enabled(mi_option_limit_os_alloc) || req_arena_id != _mi_arena_id_none()) {
|
||||
if (mi_option_is_enabled(mi_option_disallow_os_alloc) || req_arena_id != _mi_arena_id_none()) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// finally, fall back to the OS
|
||||
if (align_offset > 0) {
|
||||
return _mi_os_alloc_aligned_at_offset(size, alignment, align_offset, commit, allow_large, memid, tld->stats);
|
||||
return _mi_os_alloc_aligned_at_offset(size, alignment, align_offset, commit, allow_large, memid);
|
||||
}
|
||||
else {
|
||||
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid, tld->stats);
|
||||
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid);
|
||||
}
|
||||
}
|
||||
|
||||
void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld)
|
||||
void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid)
|
||||
{
|
||||
return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, 0, commit, allow_large, req_arena_id, memid, tld);
|
||||
return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, 0, commit, allow_large, req_arena_id, memid);
|
||||
}
|
||||
|
||||
|
||||
@@ -432,25 +473,26 @@ static long mi_arena_purge_delay(void) {
|
||||
|
||||
// reset or decommit in an arena and update the committed/decommit bitmaps
|
||||
// assumes we own the area (i.e. blocks_in_use is claimed by us)
|
||||
static void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) {
|
||||
static void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks) {
|
||||
mi_assert_internal(arena->blocks_committed != NULL);
|
||||
mi_assert_internal(arena->blocks_purge != NULL);
|
||||
mi_assert_internal(!arena->memid.is_pinned);
|
||||
const size_t size = mi_arena_block_size(blocks);
|
||||
void* const p = mi_arena_block_start(arena, bitmap_idx);
|
||||
bool needs_recommit;
|
||||
if (_mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx)) {
|
||||
size_t already_committed = 0;
|
||||
if (_mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx, &already_committed)) {
|
||||
// all blocks are committed, we can purge freely
|
||||
needs_recommit = _mi_os_purge(p, size, stats);
|
||||
mi_assert_internal(already_committed == blocks);
|
||||
needs_recommit = _mi_os_purge(p, size);
|
||||
}
|
||||
else {
|
||||
// some blocks are not committed -- this can happen when a partially committed block is freed
|
||||
// in `_mi_arena_free` and it is conservatively marked as uncommitted but still scheduled for a purge
|
||||
// we need to ensure we do not try to reset (as that may be invalid for uncommitted memory),
|
||||
// and also undo the decommit stats (as it was already adjusted)
|
||||
// we need to ensure we do not try to reset (as that may be invalid for uncommitted memory).
|
||||
mi_assert_internal(already_committed < blocks);
|
||||
mi_assert_internal(mi_option_is_enabled(mi_option_purge_decommits));
|
||||
needs_recommit = _mi_os_purge_ex(p, size, false /* allow reset? */, stats);
|
||||
_mi_stat_increase(&stats->committed, size);
|
||||
needs_recommit = _mi_os_purge_ex(p, size, false /* allow reset? */, mi_arena_block_size(already_committed));
|
||||
}
|
||||
|
||||
// clear the purged blocks
|
||||
@@ -463,32 +505,35 @@ static void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks,
|
||||
|
||||
// Schedule a purge. This is usually delayed to avoid repeated decommit/commit calls.
|
||||
// Note: assumes we (still) own the area as we may purge immediately
|
||||
static void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) {
|
||||
static void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks) {
|
||||
mi_assert_internal(arena->blocks_purge != NULL);
|
||||
const long delay = mi_arena_purge_delay();
|
||||
if (delay < 0) return; // is purging allowed at all?
|
||||
|
||||
if (_mi_preloading() || delay == 0) {
|
||||
// decommit directly
|
||||
mi_arena_purge(arena, bitmap_idx, blocks, stats);
|
||||
mi_arena_purge(arena, bitmap_idx, blocks);
|
||||
}
|
||||
else {
|
||||
// schedule decommit
|
||||
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
|
||||
if (expire != 0) {
|
||||
mi_atomic_addi64_acq_rel(&arena->purge_expire, delay/10); // add smallish extra delay
|
||||
// schedule purge
|
||||
const mi_msecs_t expire = _mi_clock_now() + delay;
|
||||
mi_msecs_t expire0 = 0;
|
||||
if (mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire0, expire)) {
|
||||
// expiration was not yet set
|
||||
// maybe set the global arenas expire as well (if it wasn't set already)
|
||||
mi_atomic_casi64_strong_acq_rel(&mi_arenas_purge_expire, &expire0, expire);
|
||||
}
|
||||
else {
|
||||
mi_atomic_storei64_release(&arena->purge_expire, _mi_clock_now() + delay);
|
||||
// already an expiration was set
|
||||
}
|
||||
_mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL);
|
||||
_mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// purge a range of blocks
|
||||
// return true if the full range was purged.
|
||||
// assumes we own the area (i.e. blocks_in_use is claimed by us)
|
||||
static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx, size_t bitlen, size_t purge, mi_stats_t* stats) {
|
||||
static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx, size_t bitlen, size_t purge) {
|
||||
const size_t endidx = startidx + bitlen;
|
||||
size_t bitidx = startidx;
|
||||
bool all_purged = false;
|
||||
@@ -501,7 +546,7 @@ static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx,
|
||||
if (count > 0) {
|
||||
// found range to be purged
|
||||
const mi_bitmap_index_t range_idx = mi_bitmap_index_create(idx, bitidx);
|
||||
mi_arena_purge(arena, range_idx, count, stats);
|
||||
mi_arena_purge(arena, range_idx, count);
|
||||
if (count == bitlen) {
|
||||
all_purged = true;
|
||||
}
|
||||
@@ -511,16 +556,25 @@ static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx,
|
||||
return all_purged;
|
||||
}
|
||||
|
||||
// returns true if anything was purged
|
||||
static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi_stats_t* stats)
|
||||
// returns
|
||||
// -1 = nothing was purged
|
||||
// 0 = nothing was purged yet because have not yet reached the expire time
|
||||
// 1 = some pages in the arena were purged
|
||||
static int mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force)
|
||||
{
|
||||
if (arena->memid.is_pinned || arena->blocks_purge == NULL) return false;
|
||||
// check pre-conditions
|
||||
if (arena->memid.is_pinned) return -1;
|
||||
|
||||
// expired yet?
|
||||
mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire);
|
||||
if (expire == 0) return false;
|
||||
if (!force && expire > now) return false;
|
||||
if (!force) {
|
||||
if (expire == 0) return -1;
|
||||
if (expire > now) return 0;
|
||||
}
|
||||
|
||||
// reset expire (if not already set concurrently)
|
||||
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, 0);
|
||||
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, (mi_msecs_t)0);
|
||||
_mi_stat_counter_increase(&_mi_stats_main.arena_purges, 1);
|
||||
|
||||
// potential purges scheduled, walk through the bitmap
|
||||
bool any_purged = false;
|
||||
@@ -535,6 +589,7 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi
|
||||
while (bitidx + bitlen < MI_BITMAP_FIELD_BITS && (purge & ((size_t)1 << (bitidx + bitlen))) != 0) {
|
||||
bitlen++;
|
||||
}
|
||||
// temporarily claim the purge range as "in-use" to be thread-safe with allocation
|
||||
// try to claim the longest range of corresponding in_use bits
|
||||
const mi_bitmap_index_t bitmap_index = mi_bitmap_index_create(i, bitidx);
|
||||
while( bitlen > 0 ) {
|
||||
@@ -547,7 +602,7 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi
|
||||
if (bitlen > 0) {
|
||||
// read purge again now that we have the in_use bits
|
||||
purge = mi_atomic_load_acquire(&arena->blocks_purge[i]);
|
||||
if (!mi_arena_purge_range(arena, i, bitidx, bitlen, purge, stats)) {
|
||||
if (!mi_arena_purge_range(arena, i, bitidx, bitlen, purge)) {
|
||||
full_purge = false;
|
||||
}
|
||||
any_purged = true;
|
||||
@@ -564,12 +619,18 @@ static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi
|
||||
mi_msecs_t expected = 0;
|
||||
mi_atomic_casi64_strong_acq_rel(&arena->purge_expire,&expected,_mi_clock_now() + delay);
|
||||
}
|
||||
return any_purged;
|
||||
return (any_purged ? 1 : -1);
|
||||
}
|
||||
|
||||
static void mi_arenas_try_purge( bool force, bool visit_all, mi_stats_t* stats ) {
|
||||
static void mi_arenas_try_purge( bool force, bool visit_all )
|
||||
{
|
||||
if (_mi_preloading() || mi_arena_purge_delay() <= 0) return; // nothing will be scheduled
|
||||
|
||||
// check if any arena needs purging?
|
||||
const mi_msecs_t now = _mi_clock_now();
|
||||
mi_msecs_t arenas_expire = mi_atomic_loadi64_acquire(&mi_arenas_purge_expire);
|
||||
if (!force && (arenas_expire == 0 || arenas_expire > now)) return;
|
||||
|
||||
const size_t max_arena = mi_atomic_load_acquire(&mi_arena_count);
|
||||
if (max_arena == 0) return;
|
||||
|
||||
@@ -577,17 +638,31 @@ static void mi_arenas_try_purge( bool force, bool visit_all, mi_stats_t* stats )
|
||||
static mi_atomic_guard_t purge_guard;
|
||||
mi_atomic_guard(&purge_guard)
|
||||
{
|
||||
mi_msecs_t now = _mi_clock_now();
|
||||
size_t max_purge_count = (visit_all ? max_arena : 1);
|
||||
// increase global expire: at most one purge per delay cycle
|
||||
mi_atomic_storei64_release(&mi_arenas_purge_expire, now + mi_arena_purge_delay());
|
||||
size_t max_purge_count = (visit_all ? max_arena : 2);
|
||||
bool all_visited = true;
|
||||
bool any_purged = false;
|
||||
for (size_t i = 0; i < max_arena; i++) {
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
|
||||
if (arena != NULL) {
|
||||
if (mi_arena_try_purge(arena, now, force, stats)) {
|
||||
if (max_purge_count <= 1) break;
|
||||
max_purge_count--;
|
||||
int purged = mi_arena_try_purge(arena, now, force);
|
||||
if (purged >= 0) { // purged, or not yet the expire-time reached
|
||||
any_purged = true;
|
||||
if (purged >= 1) { // purged at least one page
|
||||
if (max_purge_count <= 1) {
|
||||
all_visited = false;
|
||||
break;
|
||||
}
|
||||
max_purge_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (all_visited && !any_purged) {
|
||||
// all arena's were visited and nothing needed to be purged: reset global expire
|
||||
mi_atomic_storei64_release(&mi_arenas_purge_expire, (mi_msecs_t)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,20 +671,24 @@ static void mi_arenas_try_purge( bool force, bool visit_all, mi_stats_t* stats )
|
||||
Arena free
|
||||
----------------------------------------------------------- */
|
||||
|
||||
void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memid, mi_stats_t* stats) {
|
||||
mi_assert_internal(size > 0 && stats != NULL);
|
||||
void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memid) {
|
||||
mi_assert_internal(size > 0);
|
||||
mi_assert_internal(committed_size <= size);
|
||||
if (p==NULL) return;
|
||||
if (size==0) return;
|
||||
const bool all_committed = (committed_size == size);
|
||||
const size_t decommitted_size = (committed_size <= size ? size - committed_size : 0);
|
||||
|
||||
// need to set all memory to undefined as some parts may still be marked as no_access (like padding etc.)
|
||||
mi_track_mem_undefined(p,size);
|
||||
|
||||
if (mi_memkind_is_os(memid.memkind)) {
|
||||
// was a direct OS allocation, pass through
|
||||
if (!all_committed && committed_size > 0) {
|
||||
// if partially committed, adjust the committed stats (as `_mi_os_free` will increase decommit by the full size)
|
||||
_mi_stat_decrease(&stats->committed, committed_size);
|
||||
if (!all_committed && decommitted_size > 0) {
|
||||
// if partially committed, adjust the committed stats (as `_mi_os_free` will decrease commit by the full size)
|
||||
_mi_stat_increase(&_mi_stats_main.committed, decommitted_size);
|
||||
}
|
||||
_mi_os_free(p, size, memid, stats);
|
||||
_mi_os_free(p, size, memid);
|
||||
}
|
||||
else if (memid.memkind == MI_MEM_ARENA) {
|
||||
// allocated in an arena
|
||||
@@ -623,18 +702,15 @@ void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memi
|
||||
|
||||
// checks
|
||||
if (arena == NULL) {
|
||||
_mi_error_message(EINVAL, "trying to free from non-existent arena: %p, size %zu, memid: 0x%zx\n", p, size, memid);
|
||||
_mi_error_message(EINVAL, "trying to free from an invalid arena: %p, size %zu, memkind: 0x%x\n", p, size, memid.memkind);
|
||||
return;
|
||||
}
|
||||
mi_assert_internal(arena->field_count > mi_bitmap_index_field(bitmap_idx));
|
||||
if (arena->field_count <= mi_bitmap_index_field(bitmap_idx)) {
|
||||
_mi_error_message(EINVAL, "trying to free from non-existent arena block: %p, size %zu, memid: 0x%zx\n", p, size, memid);
|
||||
_mi_error_message(EINVAL, "trying to free from an invalid arena block: %p, size %zu, memid: 0x%x\n", p, size, memid.memkind);
|
||||
return;
|
||||
}
|
||||
|
||||
// need to set all memory to undefined as some parts may still be marked as no_access (like padding etc.)
|
||||
mi_track_mem_undefined(p,size);
|
||||
|
||||
// potentially decommit
|
||||
if (arena->memid.is_pinned || arena->blocks_committed == NULL) {
|
||||
mi_assert_internal(all_committed);
|
||||
@@ -644,20 +720,20 @@ void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memi
|
||||
mi_assert_internal(arena->blocks_purge != NULL);
|
||||
|
||||
if (!all_committed) {
|
||||
// mark the entire range as no longer committed (so we recommit the full range when re-using)
|
||||
// mark the entire range as no longer committed (so we will recommit the full range when re-using)
|
||||
_mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx);
|
||||
mi_track_mem_noaccess(p,size);
|
||||
if (committed_size > 0) {
|
||||
//if (committed_size > 0) {
|
||||
// if partially committed, adjust the committed stats (is it will be recommitted when re-using)
|
||||
// in the delayed purge, we now need to not count a decommit if the range is not marked as committed.
|
||||
_mi_stat_decrease(&stats->committed, committed_size);
|
||||
}
|
||||
// in the delayed purge, we do no longer decrease the commit if the range is not marked entirely as committed.
|
||||
_mi_stat_decrease(&_mi_stats_main.committed, committed_size);
|
||||
//}
|
||||
// note: if not all committed, it may be that the purge will reset/decommit the entire range
|
||||
// that contains already decommitted parts. Since purge consistently uses reset or decommit that
|
||||
// works (as we should never reset decommitted parts).
|
||||
}
|
||||
// (delay) purge the entire range
|
||||
mi_arena_schedule_purge(arena, bitmap_idx, blocks, stats);
|
||||
mi_arena_schedule_purge(arena, bitmap_idx, blocks);
|
||||
}
|
||||
|
||||
// and make it available to others again
|
||||
@@ -673,7 +749,7 @@ void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memi
|
||||
}
|
||||
|
||||
// purge expired decommits
|
||||
mi_arenas_try_purge(false, false, stats);
|
||||
mi_arenas_try_purge(false, false);
|
||||
}
|
||||
|
||||
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
|
||||
@@ -684,14 +760,15 @@ static void mi_arenas_unsafe_destroy(void) {
|
||||
for (size_t i = 0; i < max_arena; i++) {
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
|
||||
if (arena != NULL) {
|
||||
mi_lock_done(&arena->abandoned_visit_lock);
|
||||
if (arena->start != NULL && mi_memkind_is_os(arena->memid.memkind)) {
|
||||
mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], NULL);
|
||||
_mi_os_free(arena->start, mi_arena_size(arena), arena->memid, &_mi_stats_main);
|
||||
_mi_os_free(arena->start, mi_arena_size(arena), arena->memid);
|
||||
}
|
||||
else {
|
||||
new_max_arena = i;
|
||||
}
|
||||
mi_arena_meta_free(arena, arena->meta_memid, arena->meta_size, &_mi_stats_main);
|
||||
_mi_arena_meta_free(arena, arena->meta_memid, arena->meta_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,22 +778,22 @@ static void mi_arenas_unsafe_destroy(void) {
|
||||
}
|
||||
|
||||
// Purge the arenas; if `force_purge` is true, amenable parts are purged even if not yet expired
|
||||
void _mi_arena_collect(bool force_purge, mi_stats_t* stats) {
|
||||
mi_arenas_try_purge(force_purge, true /* visit all */, stats);
|
||||
void _mi_arenas_collect(bool force_purge) {
|
||||
mi_arenas_try_purge(force_purge, force_purge /* visit all? */);
|
||||
}
|
||||
|
||||
// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit`
|
||||
// for dynamic libraries that are unloaded and need to release all their allocated memory.
|
||||
void _mi_arena_unsafe_destroy_all(mi_stats_t* stats) {
|
||||
void _mi_arena_unsafe_destroy_all(void) {
|
||||
mi_arenas_unsafe_destroy();
|
||||
_mi_arena_collect(true /* force purge */, stats); // purge non-owned arenas
|
||||
_mi_arenas_collect(true /* force purge */); // purge non-owned arenas
|
||||
}
|
||||
|
||||
// Is a pointer inside any of our arenas?
|
||||
bool _mi_arena_contains(const void* p) {
|
||||
const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count);
|
||||
for (size_t i = 0; i < max_arena; i++) {
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]);
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
|
||||
if (arena != NULL && arena->start <= (const uint8_t*)p && arena->start + mi_arena_block_size(arena->block_count) > (const uint8_t*)p) {
|
||||
return true;
|
||||
}
|
||||
@@ -724,46 +801,64 @@ bool _mi_arena_contains(const void* p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Add an arena.
|
||||
----------------------------------------------------------- */
|
||||
|
||||
static bool mi_arena_add(mi_arena_t* arena, mi_arena_id_t* arena_id) {
|
||||
static bool mi_arena_add(mi_arena_t* arena, mi_arena_id_t* arena_id, mi_stats_t* stats) {
|
||||
mi_assert_internal(arena != NULL);
|
||||
mi_assert_internal((uintptr_t)mi_atomic_load_ptr_relaxed(uint8_t,&arena->start) % MI_SEGMENT_ALIGN == 0);
|
||||
mi_assert_internal(arena->block_count > 0);
|
||||
if (arena_id != NULL) { *arena_id = -1; }
|
||||
|
||||
size_t i = mi_atomic_increment_acq_rel(&mi_arena_count);
|
||||
if (i >= MI_MAX_ARENAS) {
|
||||
mi_atomic_decrement_acq_rel(&mi_arena_count);
|
||||
return false;
|
||||
size_t i = mi_atomic_load_relaxed(&mi_arena_count);
|
||||
while (i < MI_MAX_ARENAS) {
|
||||
if (mi_atomic_cas_strong_acq_rel(&mi_arena_count, &i, i+1)) {
|
||||
_mi_stat_counter_increase(&stats->arena_count, 1);
|
||||
arena->id = mi_arena_id_create(i);
|
||||
mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], arena);
|
||||
if (arena_id != NULL) { *arena_id = arena->id; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
arena->id = mi_arena_id_create(i);
|
||||
mi_atomic_store_ptr_release(mi_arena_t,&mi_arenas[i], arena);
|
||||
if (arena_id != NULL) { *arena_id = arena->id; }
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept
|
||||
{
|
||||
if (arena_id != NULL) *arena_id = _mi_arena_id_none();
|
||||
if (size < MI_ARENA_BLOCK_SIZE) return false;
|
||||
|
||||
if (size < MI_ARENA_BLOCK_SIZE) {
|
||||
_mi_warning_message("the arena size is too small (memory at %p with size %zu)\n", start, size);
|
||||
return false;
|
||||
}
|
||||
if (size > MI_MAX_ALLOC_SIZE) {
|
||||
_mi_error_message(EOVERFLOW, "the arena size is too large (memory at %p with size %zu)\n", start, size);
|
||||
return false;
|
||||
}
|
||||
if (is_large) {
|
||||
mi_assert_internal(memid.initially_committed && memid.is_pinned);
|
||||
}
|
||||
if (!_mi_is_aligned(start, MI_SEGMENT_ALIGN)) {
|
||||
void* const aligned_start = _mi_align_up_ptr(start, MI_SEGMENT_ALIGN);
|
||||
const size_t diff = (uint8_t*)aligned_start - (uint8_t*)start;
|
||||
if (diff >= size || (size - diff) < MI_ARENA_BLOCK_SIZE) {
|
||||
_mi_warning_message("after alignment, the size of the arena becomes too small (memory at %p with size %zu)\n", start, size);
|
||||
return false;
|
||||
}
|
||||
start = aligned_start;
|
||||
size = size - diff;
|
||||
}
|
||||
|
||||
const size_t bcount = size / MI_ARENA_BLOCK_SIZE;
|
||||
const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS);
|
||||
const size_t bitmaps = (memid.is_pinned ? 2 : 4);
|
||||
const size_t bitmaps = (memid.is_pinned ? 3 : 5);
|
||||
const size_t asize = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t));
|
||||
mi_memid_t meta_memid;
|
||||
mi_arena_t* arena = (mi_arena_t*)mi_arena_meta_zalloc(asize, &meta_memid, &_mi_stats_main); // TODO: can we avoid allocating from the OS?
|
||||
mi_arena_t* arena = (mi_arena_t*)_mi_arena_meta_zalloc(asize, &meta_memid);
|
||||
if (arena == NULL) return false;
|
||||
|
||||
// already zero'd due to os_alloc
|
||||
// already zero'd due to zalloc
|
||||
// _mi_memzero(arena, asize);
|
||||
arena->id = _mi_arena_id_none();
|
||||
arena->memid = memid;
|
||||
@@ -777,9 +872,12 @@ static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int
|
||||
arena->is_large = is_large;
|
||||
arena->purge_expire = 0;
|
||||
arena->search_idx = 0;
|
||||
arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap
|
||||
arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[2*fields]); // just after dirty bitmap
|
||||
arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after committed bitmap
|
||||
mi_lock_init(&arena->abandoned_visit_lock);
|
||||
// consecutive bitmaps
|
||||
arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap
|
||||
arena->blocks_abandoned = &arena->blocks_inuse[2 * fields]; // just after dirty bitmap
|
||||
arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after abandoned bitmap
|
||||
arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[4*fields]); // just after committed bitmap
|
||||
// initialize committed bitmap?
|
||||
if (arena->blocks_committed != NULL && arena->memid.initially_committed) {
|
||||
memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning
|
||||
@@ -793,7 +891,7 @@ static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int
|
||||
mi_bitmap_index_t postidx = mi_bitmap_index_create(fields - 1, MI_BITMAP_FIELD_BITS - post);
|
||||
_mi_bitmap_claim(arena->blocks_inuse, fields, post, postidx, NULL);
|
||||
}
|
||||
return mi_arena_add(arena, arena_id);
|
||||
return mi_arena_add(arena, arena_id, &_mi_stats_main);
|
||||
|
||||
}
|
||||
|
||||
@@ -808,14 +906,20 @@ bool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is
|
||||
// Reserve a range of regular OS memory
|
||||
int mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept {
|
||||
if (arena_id != NULL) *arena_id = _mi_arena_id_none();
|
||||
size = _mi_align_up(size, MI_ARENA_BLOCK_SIZE); // at least one block
|
||||
if (size < MI_MAX_ALLOC_SIZE) {
|
||||
size = _mi_align_up(size, MI_ARENA_BLOCK_SIZE); // at least one block
|
||||
}
|
||||
if (size > MI_MAX_ALLOC_SIZE) {
|
||||
_mi_error_message(EOVERFLOW, "memory reservation request is too large (size %zu)\n", size);
|
||||
return ENOMEM;
|
||||
}
|
||||
mi_memid_t memid;
|
||||
void* start = _mi_os_alloc_aligned(size, MI_SEGMENT_ALIGN, commit, allow_large, &memid, &_mi_stats_main);
|
||||
void* start = _mi_os_alloc_aligned(size, MI_SEGMENT_ALIGN, commit, allow_large, &memid);
|
||||
if (start == NULL) return ENOMEM;
|
||||
const bool is_large = memid.is_pinned; // todo: use separate is_large field?
|
||||
if (!mi_manage_os_memory_ex2(start, size, is_large, -1 /* numa node */, exclusive, memid, arena_id)) {
|
||||
_mi_os_free_ex(start, size, commit, memid, &_mi_stats_main);
|
||||
_mi_verbose_message("failed to reserve %zu k memory\n", _mi_divide_up(size, 1024));
|
||||
_mi_os_free_ex(start, size, commit, memid);
|
||||
_mi_verbose_message("failed to reserve %zu KiB memory\n", _mi_divide_up(size, 1024));
|
||||
return ENOMEM;
|
||||
}
|
||||
_mi_verbose_message("reserved %zu KiB memory%s\n", _mi_divide_up(size, 1024), is_large ? " (in large os pages)" : "");
|
||||
@@ -838,32 +942,61 @@ int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noe
|
||||
Debugging
|
||||
----------------------------------------------------------- */
|
||||
|
||||
static size_t mi_debug_show_bitmap(const char* prefix, mi_bitmap_field_t* fields, size_t field_count ) {
|
||||
static size_t mi_debug_show_bitmap(const char* prefix, const char* header, size_t block_count, mi_bitmap_field_t* fields, size_t field_count ) {
|
||||
_mi_message("%s%s:\n", prefix, header);
|
||||
size_t bcount = 0;
|
||||
size_t inuse_count = 0;
|
||||
for (size_t i = 0; i < field_count; i++) {
|
||||
char buf[MI_BITMAP_FIELD_BITS + 1];
|
||||
uintptr_t field = mi_atomic_load_relaxed(&fields[i]);
|
||||
for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++) {
|
||||
bool inuse = ((((uintptr_t)1 << bit) & field) != 0);
|
||||
if (inuse) inuse_count++;
|
||||
buf[MI_BITMAP_FIELD_BITS - 1 - bit] = (inuse ? 'x' : '.');
|
||||
for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++, bcount++) {
|
||||
if (bcount < block_count) {
|
||||
bool inuse = ((((uintptr_t)1 << bit) & field) != 0);
|
||||
if (inuse) inuse_count++;
|
||||
buf[bit] = (inuse ? 'x' : '.');
|
||||
}
|
||||
else {
|
||||
buf[bit] = ' ';
|
||||
}
|
||||
}
|
||||
buf[MI_BITMAP_FIELD_BITS] = 0;
|
||||
_mi_verbose_message("%s%s\n", prefix, buf);
|
||||
_mi_message("%s %s\n", prefix, buf);
|
||||
}
|
||||
_mi_message("%s total ('x'): %zu\n", prefix, inuse_count);
|
||||
return inuse_count;
|
||||
}
|
||||
|
||||
void mi_debug_show_arenas(void) mi_attr_noexcept {
|
||||
const bool show_inuse = true;
|
||||
size_t max_arenas = mi_atomic_load_relaxed(&mi_arena_count);
|
||||
size_t inuse_total = 0;
|
||||
//size_t abandoned_total = 0;
|
||||
//size_t purge_total = 0;
|
||||
for (size_t i = 0; i < max_arenas; i++) {
|
||||
mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]);
|
||||
if (arena == NULL) break;
|
||||
size_t inuse_count = 0;
|
||||
_mi_verbose_message("arena %zu: %zu blocks with %zu fields\n", i, arena->block_count, arena->field_count);
|
||||
inuse_count += mi_debug_show_bitmap(" ", arena->blocks_inuse, arena->field_count);
|
||||
_mi_verbose_message(" blocks in use ('x'): %zu\n", inuse_count);
|
||||
_mi_message("arena %zu: %zu blocks of size %zuMiB (in %zu fields) %s\n", i, arena->block_count, (size_t)(MI_ARENA_BLOCK_SIZE / MI_MiB), arena->field_count, (arena->memid.is_pinned ? ", pinned" : ""));
|
||||
if (show_inuse) {
|
||||
inuse_total += mi_debug_show_bitmap(" ", "inuse blocks", arena->block_count, arena->blocks_inuse, arena->field_count);
|
||||
}
|
||||
if (arena->blocks_committed != NULL) {
|
||||
mi_debug_show_bitmap(" ", "committed blocks", arena->block_count, arena->blocks_committed, arena->field_count);
|
||||
}
|
||||
//if (show_abandoned) {
|
||||
// abandoned_total += mi_debug_show_bitmap(" ", "abandoned blocks", arena->block_count, arena->blocks_abandoned, arena->field_count);
|
||||
//}
|
||||
//if (show_purge && arena->blocks_purge != NULL) {
|
||||
// purge_total += mi_debug_show_bitmap(" ", "purgeable blocks", arena->block_count, arena->blocks_purge, arena->field_count);
|
||||
//}
|
||||
}
|
||||
if (show_inuse) _mi_message("total inuse blocks : %zu\n", inuse_total);
|
||||
//if (show_abandoned) _mi_message("total abandoned blocks: %zu\n", abandoned_total);
|
||||
//if (show_purge) _mi_message("total purgeable blocks: %zu\n", purge_total);
|
||||
}
|
||||
|
||||
|
||||
void mi_arenas_print(void) mi_attr_noexcept {
|
||||
mi_debug_show_arenas();
|
||||
}
|
||||
|
||||
|
||||
@@ -887,7 +1020,7 @@ int mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_m
|
||||
_mi_verbose_message("numa node %i: reserved %zu GiB huge pages (of the %zu GiB requested)\n", numa_node, pages_reserved, pages);
|
||||
|
||||
if (!mi_manage_os_memory_ex2(p, hsize, true, numa_node, exclusive, memid, arena_id)) {
|
||||
_mi_os_free(p, hsize, memid, &_mi_stats_main);
|
||||
_mi_os_free(p, hsize, memid);
|
||||
return ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
@@ -902,17 +1035,17 @@ int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t
|
||||
if (pages == 0) return 0;
|
||||
|
||||
// pages per numa node
|
||||
size_t numa_count = (numa_nodes > 0 ? numa_nodes : _mi_os_numa_node_count());
|
||||
if (numa_count <= 0) numa_count = 1;
|
||||
int numa_count = (numa_nodes > 0 && numa_nodes <= INT_MAX ? (int)numa_nodes : _mi_os_numa_node_count());
|
||||
if (numa_count == 0) numa_count = 1;
|
||||
const size_t pages_per = pages / numa_count;
|
||||
const size_t pages_mod = pages % numa_count;
|
||||
const size_t timeout_per = (timeout_msecs==0 ? 0 : (timeout_msecs / numa_count) + 50);
|
||||
|
||||
// reserve evenly among numa nodes
|
||||
for (size_t numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) {
|
||||
for (int numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) {
|
||||
size_t node_pages = pages_per; // can be 0
|
||||
if (numa_node < pages_mod) node_pages++;
|
||||
int err = mi_reserve_huge_os_pages_at(node_pages, (int)numa_node, timeout_per);
|
||||
if ((size_t)numa_node < pages_mod) node_pages++;
|
||||
int err = mi_reserve_huge_os_pages_at(node_pages, numa_node, timeout_per);
|
||||
if (err) return err;
|
||||
if (pages < node_pages) {
|
||||
pages = 0;
|
||||
|
||||
+20
-13
@@ -81,7 +81,7 @@ inline bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, cons
|
||||
// on to the next bit range
|
||||
#ifdef MI_HAVE_FAST_BITSCAN
|
||||
mi_assert_internal(mapm != 0);
|
||||
const size_t shift = (count == 1 ? 1 : (MI_INTPTR_BITS - mi_clz(mapm) - bitidx));
|
||||
const size_t shift = (count == 1 ? 1 : (MI_SIZE_BITS - mi_clz(mapm) - bitidx));
|
||||
mi_assert_internal(shift > 0 && shift <= count);
|
||||
#else
|
||||
const size_t shift = 1;
|
||||
@@ -108,7 +108,7 @@ bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fiel
|
||||
return false;
|
||||
}
|
||||
|
||||
// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fulfilled
|
||||
// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled
|
||||
bool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields,
|
||||
const size_t start_field_idx, const size_t count,
|
||||
mi_bitmap_pred_fun_t pred_fun, void* pred_arg,
|
||||
@@ -279,6 +279,7 @@ rollback:
|
||||
newmap = (map & ~initial_mask);
|
||||
} while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap));
|
||||
}
|
||||
mi_stat_counter_increase(_mi_stats_main.arena_rollback_count,1);
|
||||
// retry? (we make a recursive call instead of goto to be able to use const declarations)
|
||||
if (retries <= 2) {
|
||||
return mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, retries+1, bitmap_idx);
|
||||
@@ -366,7 +367,7 @@ bool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t
|
||||
|
||||
// Set `count` bits at `bitmap_idx` to 1 atomically
|
||||
// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.
|
||||
bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero) {
|
||||
bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero, size_t* already_set) {
|
||||
size_t idx = mi_bitmap_index_field(bitmap_idx);
|
||||
size_t pre_mask;
|
||||
size_t mid_mask;
|
||||
@@ -374,28 +375,31 @@ bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t co
|
||||
size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);
|
||||
bool all_zero = true;
|
||||
bool any_zero = false;
|
||||
size_t one_count = 0;
|
||||
_Atomic(size_t)*field = &bitmap[idx];
|
||||
size_t prev = mi_atomic_or_acq_rel(field++, pre_mask);
|
||||
if ((prev & pre_mask) != 0) all_zero = false;
|
||||
if ((prev & pre_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & pre_mask); }
|
||||
if ((prev & pre_mask) != pre_mask) any_zero = true;
|
||||
while (mid_count-- > 0) {
|
||||
prev = mi_atomic_or_acq_rel(field++, mid_mask);
|
||||
if ((prev & mid_mask) != 0) all_zero = false;
|
||||
if ((prev & mid_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & mid_mask); }
|
||||
if ((prev & mid_mask) != mid_mask) any_zero = true;
|
||||
}
|
||||
if (post_mask!=0) {
|
||||
prev = mi_atomic_or_acq_rel(field, post_mask);
|
||||
if ((prev & post_mask) != 0) all_zero = false;
|
||||
if ((prev & post_mask) != 0) { all_zero = false; one_count += mi_popcount(prev & post_mask); }
|
||||
if ((prev & post_mask) != post_mask) any_zero = true;
|
||||
}
|
||||
if (pany_zero != NULL) { *pany_zero = any_zero; }
|
||||
if (already_set != NULL) { *already_set = one_count; };
|
||||
mi_assert_internal(all_zero ? one_count == 0 : one_count <= count);
|
||||
return all_zero;
|
||||
}
|
||||
|
||||
|
||||
// Returns `true` if all `count` bits were 1.
|
||||
// `any_ones` is `true` if there was at least one bit set to one.
|
||||
static bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_ones) {
|
||||
static bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_ones, size_t* already_set) {
|
||||
size_t idx = mi_bitmap_index_field(bitmap_idx);
|
||||
size_t pre_mask;
|
||||
size_t mid_mask;
|
||||
@@ -403,30 +407,33 @@ static bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_field
|
||||
size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask);
|
||||
bool all_ones = true;
|
||||
bool any_ones = false;
|
||||
size_t one_count = 0;
|
||||
mi_bitmap_field_t* field = &bitmap[idx];
|
||||
size_t prev = mi_atomic_load_relaxed(field++);
|
||||
if ((prev & pre_mask) != pre_mask) all_ones = false;
|
||||
if ((prev & pre_mask) != 0) any_ones = true;
|
||||
if ((prev & pre_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & pre_mask); }
|
||||
while (mid_count-- > 0) {
|
||||
prev = mi_atomic_load_relaxed(field++);
|
||||
if ((prev & mid_mask) != mid_mask) all_ones = false;
|
||||
if ((prev & mid_mask) != 0) any_ones = true;
|
||||
if ((prev & mid_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & mid_mask); }
|
||||
}
|
||||
if (post_mask!=0) {
|
||||
prev = mi_atomic_load_relaxed(field);
|
||||
if ((prev & post_mask) != post_mask) all_ones = false;
|
||||
if ((prev & post_mask) != 0) any_ones = true;
|
||||
if ((prev & post_mask) != 0) { any_ones = true; one_count += mi_popcount(prev & post_mask); }
|
||||
}
|
||||
if (pany_ones != NULL) { *pany_ones = any_ones; }
|
||||
if (already_set != NULL) { *already_set = one_count; }
|
||||
mi_assert_internal(all_ones ? one_count == count : one_count < count);
|
||||
return all_ones;
|
||||
}
|
||||
|
||||
bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
|
||||
return mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, NULL);
|
||||
bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, size_t* already_set) {
|
||||
return mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, NULL, already_set);
|
||||
}
|
||||
|
||||
bool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) {
|
||||
bool any_ones;
|
||||
mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, &any_ones);
|
||||
mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, &any_ones, NULL);
|
||||
return any_ones;
|
||||
}
|
||||
@@ -35,9 +35,13 @@ typedef mi_bitmap_field_t* mi_bitmap_t;
|
||||
typedef size_t mi_bitmap_index_t;
|
||||
|
||||
// Create a bit index.
|
||||
static inline mi_bitmap_index_t mi_bitmap_index_create_ex(size_t idx, size_t bitidx) {
|
||||
mi_assert_internal(bitidx <= MI_BITMAP_FIELD_BITS);
|
||||
return (idx*MI_BITMAP_FIELD_BITS) + bitidx;
|
||||
}
|
||||
static inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx) {
|
||||
mi_assert_internal(bitidx < MI_BITMAP_FIELD_BITS);
|
||||
return (idx*MI_BITMAP_FIELD_BITS) + bitidx;
|
||||
return mi_bitmap_index_create_ex(idx,bitidx);
|
||||
}
|
||||
|
||||
// Create a bit index.
|
||||
@@ -72,7 +76,7 @@ bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_
|
||||
// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields.
|
||||
bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx);
|
||||
|
||||
// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fulfilled
|
||||
// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled
|
||||
typedef bool (mi_cdecl *mi_bitmap_pred_fun_t)(mi_bitmap_index_t bitmap_idx, void* pred_arg);
|
||||
bool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_pred_fun_t pred_fun, void* pred_arg, mi_bitmap_index_t* bitmap_idx);
|
||||
|
||||
@@ -107,9 +111,9 @@ bool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t
|
||||
|
||||
// Set `count` bits at `bitmap_idx` to 1 atomically
|
||||
// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit.
|
||||
bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero);
|
||||
bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero, size_t* already_set);
|
||||
|
||||
bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);
|
||||
bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, size_t* already_set);
|
||||
bool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,646 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2024, 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.
|
||||
-----------------------------------------------------------------------------*/
|
||||
#if !defined(MI_IN_ALLOC_C)
|
||||
#error "this file should be included from 'alloc.c' (so aliases can work from alloc-override)"
|
||||
// add includes help an IDE
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/prim.h" // _mi_prim_thread_id()
|
||||
#endif
|
||||
|
||||
// forward declarations
|
||||
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block);
|
||||
static bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block);
|
||||
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool was_guarded);
|
||||
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block);
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Free
|
||||
// ------------------------------------------------------
|
||||
|
||||
// forward declaration of multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)
|
||||
static mi_decl_noinline void mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block, void* p, bool was_guarded);
|
||||
|
||||
// regular free of a (thread local) block pointer
|
||||
// fast path written carefully to prevent spilling on the stack
|
||||
static inline void mi_free_block_local(mi_page_t* page, mi_block_t* block, bool was_guarded, bool track_stats, bool check_full)
|
||||
{
|
||||
MI_UNUSED(was_guarded);
|
||||
// checks
|
||||
if mi_unlikely(mi_check_is_double_free(page, block)) return;
|
||||
if (!was_guarded) { mi_check_padding(page, block); }
|
||||
if (track_stats) { mi_stat_free(page, block); }
|
||||
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN
|
||||
if (!mi_page_is_huge(page)) { // huge page content may be already decommitted
|
||||
size_t dbgsize = mi_page_block_size(page); if (dbgsize > 1*MI_MiB) { dbgsize = 1*MI_MiB; }
|
||||
mi_debug_fill(page, block, MI_DEBUG_FREED, dbgsize);
|
||||
}
|
||||
#endif
|
||||
if (track_stats) { mi_track_free_size(block, mi_page_usable_size_of(page, block, was_guarded)); } // faster then mi_usable_size as we already know the page and that p is unaligned
|
||||
|
||||
// actual free: push on the local free list
|
||||
mi_block_set_next(page, block, page->local_free);
|
||||
page->local_free = block;
|
||||
if mi_unlikely(--page->used == 0) {
|
||||
_mi_page_retire(page);
|
||||
}
|
||||
else if mi_unlikely(check_full && mi_page_is_in_full(page)) {
|
||||
_mi_page_unfull(page);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust a block that was allocated aligned, to the actual start of the block in the page.
|
||||
// note: this can be called from `mi_free_generic_mt` where a non-owning thread accesses the
|
||||
// `page_start` and `block_size` fields; however these are constant and the page won't be
|
||||
// deallocated (as the block we are freeing keeps it alive) and thus safe to read concurrently.
|
||||
mi_block_t* _mi_page_ptr_unalign(const mi_page_t* page, const void* p) {
|
||||
mi_assert_internal(page!=NULL && p!=NULL);
|
||||
|
||||
size_t diff = (uint8_t*)p - page->page_start;
|
||||
size_t adjust = 0;
|
||||
if mi_likely(page->block_size_shift != 0) {
|
||||
adjust = diff & (((size_t)1 << page->block_size_shift) - 1);
|
||||
}
|
||||
else {
|
||||
const size_t block_size = page->block_size;
|
||||
if mi_likely(block_size != 0) {
|
||||
adjust = diff % block_size;
|
||||
}
|
||||
else {
|
||||
_mi_error_message(EFAULT, "reading from invalid page, possibly corrupted meta-data (address=%p, page=%p)\n", p, page);
|
||||
}
|
||||
}
|
||||
|
||||
return (mi_block_t*)((uintptr_t)p - adjust);
|
||||
}
|
||||
|
||||
static inline mi_block_t* mi_validate_block_from_ptr( const mi_page_t* page, const void* p ) {
|
||||
mi_assert(_mi_page_ptr_unalign(page,p) == (mi_block_t*)p); // should never be an interior pointer
|
||||
#if MI_SECURE > 0
|
||||
// in secure mode we always unalign to guard against free-ing interior pointers
|
||||
return _mi_page_ptr_unalign(page,p);
|
||||
#else
|
||||
MI_UNUSED(page);
|
||||
return (mi_block_t*)p;
|
||||
#endif
|
||||
}
|
||||
|
||||
// forward declaration for a MI_GUARDED build
|
||||
#if MI_GUARDED
|
||||
static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p); // forward declaration
|
||||
static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {
|
||||
if (mi_block_ptr_is_guarded(block, p)) {
|
||||
mi_block_unguard(page, block, p);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline bool mi_block_check_unguard(mi_page_t* page, mi_block_t* block, void* p) {
|
||||
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(p);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// free a local pointer (page parameter comes first for better codegen)
|
||||
static void mi_decl_noinline mi_free_generic_local(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept {
|
||||
MI_UNUSED(segment);
|
||||
mi_block_t* const block = (mi_page_has_aligned(page) ? _mi_page_ptr_unalign(page, p) : mi_validate_block_from_ptr(page,p));
|
||||
const bool was_guarded = mi_block_check_unguard(page, block, p);
|
||||
mi_free_block_local(page, block, was_guarded, true /* track stats */, true /* check for a full page */);
|
||||
}
|
||||
|
||||
// free a pointer owned by another thread (page parameter comes first for better codegen)
|
||||
static void mi_decl_noinline mi_free_generic_mt(mi_page_t* page, mi_segment_t* segment, void* p) mi_attr_noexcept {
|
||||
mi_block_t* const block = _mi_page_ptr_unalign(page, p); // don't check `has_aligned` flag to avoid a race (issue #865)
|
||||
const bool was_guarded = mi_block_check_unguard(page, block, p);
|
||||
mi_free_block_mt(page, segment, block, p, was_guarded);
|
||||
}
|
||||
|
||||
// generic free (for runtime integration)
|
||||
void mi_decl_noinline _mi_free_generic(mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept {
|
||||
if (is_local) mi_free_generic_local(page,segment,p);
|
||||
else mi_free_generic_mt(page,segment,p);
|
||||
}
|
||||
|
||||
// Get the segment data belonging to a pointer
|
||||
// This is just a single `and` in release mode but does further checks in debug mode
|
||||
// (and secure mode) to see if this was a valid pointer.
|
||||
static inline mi_segment_t* mi_checked_ptr_segment(const void* p, const char* msg)
|
||||
{
|
||||
MI_UNUSED(msg);
|
||||
|
||||
#if (MI_DEBUG>0)
|
||||
if mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0 && !mi_option_is_enabled(mi_option_guarded_precise)) {
|
||||
_mi_error_message(EINVAL, "%s: invalid (unaligned) pointer: %p\n", msg, p);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
mi_segment_t* const segment = _mi_ptr_segment(p);
|
||||
if mi_unlikely(segment==NULL) return segment;
|
||||
|
||||
#if 0 && (MI_DEBUG>0) // CPython: disabled, the warning is noisy and not actionable for Python allocations
|
||||
if mi_unlikely(!mi_is_in_heap_region(p)) {
|
||||
#if (MI_INTPTR_SIZE == 8 && defined(__linux__))
|
||||
if (((uintptr_t)p >> 40) != 0x7F) { // linux tends to align large blocks above 0x7F000000000 (issue #640)
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
_mi_warning_message("%s: pointer might not point to a valid heap region: %p\n"
|
||||
"(this may still be a valid very large allocation (over 64MiB))\n", msg, p);
|
||||
if mi_likely(_mi_ptr_cookie(segment) == segment->cookie) {
|
||||
_mi_warning_message("(yes, the previous pointer %p was valid after all)\n", p);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if (MI_DEBUG>0 || MI_SECURE>=3)
|
||||
if mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie) {
|
||||
_mi_error_message(EINVAL, "%s: pointer does not point to a valid heap space: %p\n", msg, p);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
// Free a block
|
||||
// Fast path written carefully to prevent register spilling on the stack
|
||||
static inline void mi_free_ex(void* p, size_t* pblock_size) mi_attr_noexcept
|
||||
{
|
||||
mi_segment_t* const segment = mi_checked_ptr_segment(p,"mi_free");
|
||||
if mi_unlikely(segment==NULL) {
|
||||
if (pblock_size!=NULL) { *pblock_size = 0; }
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_local = (_mi_prim_thread_id() == mi_atomic_load_relaxed(&segment->thread_id));
|
||||
mi_page_t* const page = _mi_segment_page_of(segment, p);
|
||||
if (pblock_size!=NULL) { *pblock_size = mi_page_block_size(page); }
|
||||
|
||||
if mi_likely(is_local) { // thread-local free?
|
||||
if mi_likely(page->flags.full_aligned == 0) { // and it is not a full page (full pages need to move from the full bin), nor has aligned blocks (aligned blocks need to be unaligned)
|
||||
// thread-local, aligned, and not a full page
|
||||
mi_block_t* const block = mi_validate_block_from_ptr(page,p);
|
||||
mi_free_block_local(page, block, false /* is guarded */, true /* track stats */, false /* no need to check if the page is full */);
|
||||
}
|
||||
else {
|
||||
// page is full or contains (inner) aligned blocks; use generic path
|
||||
mi_free_generic_local(page, segment, p);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// not thread-local; use generic path
|
||||
mi_free_generic_mt(page, segment, p);
|
||||
}
|
||||
}
|
||||
|
||||
void mi_free(void* p) mi_attr_noexcept {
|
||||
mi_free_ex(p,NULL);
|
||||
}
|
||||
|
||||
void mi_ufree(void* p, size_t* pblock_size) mi_attr_noexcept {
|
||||
mi_free_ex(p,pblock_size);
|
||||
}
|
||||
|
||||
void mi_free_small(void* p) mi_attr_noexcept {
|
||||
mi_free(p);
|
||||
}
|
||||
|
||||
|
||||
// return true if successful
|
||||
bool _mi_free_delayed_block(mi_block_t* block) {
|
||||
// get segment and page
|
||||
mi_assert_internal(block!=NULL);
|
||||
const mi_segment_t* const segment = _mi_ptr_segment(block);
|
||||
mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie);
|
||||
#ifndef Py_GIL_DISABLED
|
||||
// CPython: the GC traverses heaps of other threads, which can trigger this assert.
|
||||
mi_assert_internal(_mi_thread_id() == segment->thread_id);
|
||||
#endif
|
||||
mi_page_t* const page = _mi_segment_page_of(segment, block);
|
||||
|
||||
// Clear the no-delayed flag so delayed freeing is used again for this page.
|
||||
// This must be done before collecting the free lists on this page -- otherwise
|
||||
// some blocks may end up in the page `thread_free` list with no blocks in the
|
||||
// heap `thread_delayed_free` list which may cause the page to be never freed!
|
||||
// (it would only be freed if we happen to scan it in `mi_page_queue_find_free_ex`)
|
||||
if (!_mi_page_try_use_delayed_free(page, MI_USE_DELAYED_FREE, false /* dont overwrite never delayed */)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// collect all other non-local frees (move from `thread_free` to `free`) to ensure up-to-date `used` count
|
||||
_mi_page_free_collect(page, false);
|
||||
|
||||
// and free the block (possibly freeing the page as well since `used` is updated)
|
||||
mi_free_block_local(page, block, false /* is guarded */, false /* stats have already been adjusted */, true /* check for a full page */);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Multi-threaded Free (`_mt`)
|
||||
// ------------------------------------------------------
|
||||
|
||||
// Push a block that is owned by another thread on its page-local thread free
|
||||
// list or it's heap delayed free list. Such blocks are later collected by
|
||||
// the owning thread in `_mi_free_delayed_block`.
|
||||
static void mi_decl_noinline mi_free_block_delayed_mt( mi_page_t* page, mi_block_t* block )
|
||||
{
|
||||
// Try to put the block on either the page-local thread free list,
|
||||
// or the heap delayed free list (if this is the first non-local free in that page)
|
||||
mi_thread_free_t tfreex;
|
||||
bool use_delayed;
|
||||
mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free);
|
||||
do {
|
||||
use_delayed = (mi_tf_delayed(tfree) == MI_USE_DELAYED_FREE);
|
||||
if mi_unlikely(use_delayed) {
|
||||
// unlikely: this only happens on the first concurrent free in a page that is in the full list
|
||||
tfreex = mi_tf_set_delayed(tfree,MI_DELAYED_FREEING);
|
||||
}
|
||||
else {
|
||||
// usual: directly add to page thread_free list
|
||||
mi_block_set_next(page, block, mi_tf_block(tfree));
|
||||
tfreex = mi_tf_set_block(tfree,block);
|
||||
}
|
||||
} while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));
|
||||
|
||||
// If this was the first non-local free, we need to push it on the heap delayed free list instead
|
||||
if mi_unlikely(use_delayed) {
|
||||
// racy read on `heap`, but ok because MI_DELAYED_FREEING is set (see `mi_heap_delete` and `mi_heap_collect_abandon`)
|
||||
mi_heap_t* const heap = (mi_heap_t*)(mi_atomic_load_acquire(&page->xheap)); //mi_page_heap(page);
|
||||
mi_assert_internal(heap != NULL);
|
||||
if (heap != NULL) {
|
||||
// add to the delayed free list of this heap. (do this atomically as the lock only protects heap memory validity)
|
||||
mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);
|
||||
do {
|
||||
mi_block_set_nextx(heap,block,dfree, heap->keys);
|
||||
} while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block));
|
||||
}
|
||||
|
||||
// and reset the MI_DELAYED_FREEING flag
|
||||
tfree = mi_atomic_load_relaxed(&page->xthread_free);
|
||||
do {
|
||||
tfreex = tfree;
|
||||
mi_assert_internal(mi_tf_delayed(tfree) == MI_DELAYED_FREEING);
|
||||
tfreex = mi_tf_set_delayed(tfree,MI_NO_DELAYED_FREE);
|
||||
} while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-threaded free (`_mt`) (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON)
|
||||
static void mi_decl_noinline mi_free_block_mt(mi_page_t* page, mi_segment_t* segment, mi_block_t* block, void* p, bool was_guarded)
|
||||
{
|
||||
MI_UNUSED(was_guarded);
|
||||
|
||||
// first see if the segment was abandoned and if we can reclaim it into our thread
|
||||
if (_mi_option_get_fast(mi_option_abandoned_reclaim_on_free) != 0 &&
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
segment->page_kind != MI_PAGE_HUGE &&
|
||||
#endif
|
||||
mi_atomic_load_relaxed(&segment->thread_id) == 0 && // segment is abandoned?
|
||||
mi_prim_get_default_heap() != (mi_heap_t*)&_mi_heap_empty) // and we did not already exit this thread (without this check, a fresh heap will be initalized (issue #944))
|
||||
{
|
||||
// the segment is abandoned, try to reclaim it into our heap
|
||||
if (_mi_segment_attempt_reclaim(mi_heap_get_default(), segment)) {
|
||||
mi_assert_internal(_mi_thread_id() == mi_atomic_load_relaxed(&segment->thread_id));
|
||||
mi_assert_internal(mi_heap_get_default()->tld->segments.subproc == segment->subproc);
|
||||
mi_free(p); // recursively free as now it will be a local free in our heap
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The padding check may access the non-thread-owned page for the key values.
|
||||
// that is safe as these are constant and the page won't be freed (as the block is not freed yet).
|
||||
if (!was_guarded) { mi_check_padding(page, block); }
|
||||
|
||||
// adjust stats (after padding check and potentially recursive `mi_free` above)
|
||||
mi_stat_free(page, block); // stat_free may access the padding
|
||||
mi_track_free_size(block, mi_page_usable_size_of(page,block,was_guarded));
|
||||
|
||||
// for small size, ensure we can fit the delayed thread pointers without triggering overflow detection
|
||||
_mi_padding_shrink(page, block, sizeof(mi_block_t));
|
||||
|
||||
if (segment->kind == MI_SEGMENT_HUGE) {
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
// huge page segments are always abandoned and can be freed immediately
|
||||
_mi_segment_huge_page_free(segment, page, block);
|
||||
return;
|
||||
#else
|
||||
// huge pages are special as they occupy the entire segment
|
||||
// as these are large we reset the memory occupied by the page so it is available to other threads
|
||||
// (as the owning thread needs to actually free the memory later).
|
||||
_mi_segment_huge_page_reset(segment, page, block);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading
|
||||
size_t dbgsize = mi_usable_size(block); if (dbgsize > 1*MI_MiB) { dbgsize = 1*MI_MiB; }
|
||||
mi_debug_fill(page, block, MI_DEBUG_FREED, dbgsize);
|
||||
#endif
|
||||
}
|
||||
|
||||
// and finally free the actual block by pushing it on the owning heap
|
||||
// thread_delayed free list (or heap delayed free list)
|
||||
mi_free_block_delayed_mt(page,block);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Usable size
|
||||
// ------------------------------------------------------
|
||||
|
||||
// Bytes available in a block
|
||||
static size_t mi_decl_noinline mi_page_usable_aligned_size_of(const mi_page_t* page, const void* p) mi_attr_noexcept {
|
||||
const mi_block_t* block = _mi_page_ptr_unalign(page, p);
|
||||
const bool is_guarded = mi_block_ptr_is_guarded(block,p);
|
||||
const size_t size = mi_page_usable_size_of(page, block, is_guarded);
|
||||
mi_assert_internal((void*)p >= (void*)block);
|
||||
const size_t adjust = (uint8_t*)p - (uint8_t*)block;
|
||||
mi_assert_internal(adjust <= size);
|
||||
const size_t aligned_size = (adjust <= size ? size - adjust : 0); // size can be zero if the padding is corrupted
|
||||
return aligned_size;
|
||||
}
|
||||
|
||||
static inline mi_page_t* mi_validate_ptr_page(const void* p, const char* msg) {
|
||||
const mi_segment_t* const segment = mi_checked_ptr_segment(p, msg);
|
||||
if mi_unlikely(segment==NULL) return NULL;
|
||||
mi_page_t* const page = _mi_segment_page_of(segment, p);
|
||||
return page;
|
||||
}
|
||||
|
||||
size_t _mi_page_usable_size(const mi_page_t* page, const void* p) mi_attr_noexcept {
|
||||
if mi_unlikely(page==NULL) return 0;
|
||||
mi_assert_internal(mi_validate_ptr_page(p,"_mi_page_usable_size") == page);
|
||||
if mi_likely(!mi_page_has_aligned(page)) {
|
||||
const mi_block_t* block = mi_validate_block_from_ptr(page,p);
|
||||
return mi_page_usable_size_of(page, block, false /* is guarded */);
|
||||
}
|
||||
else {
|
||||
// split out to separate routine for improved code generation
|
||||
return mi_page_usable_aligned_size_of(page, p);
|
||||
}
|
||||
}
|
||||
|
||||
mi_decl_nodiscard size_t mi_usable_size(const void* p) mi_attr_noexcept {
|
||||
const mi_page_t* const page = mi_validate_ptr_page(p,"mi_usable_size");
|
||||
return _mi_page_usable_size(page,p);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Free variants
|
||||
// ------------------------------------------------------
|
||||
|
||||
void mi_free_size(void* p, size_t size) mi_attr_noexcept {
|
||||
MI_UNUSED_RELEASE(size);
|
||||
#if MI_DEBUG
|
||||
const mi_page_t* const page = mi_validate_ptr_page(p,"mi_free_size");
|
||||
const size_t available = _mi_page_usable_size(page,p);
|
||||
mi_assert(p == NULL || size <= available || available == 0 /* invalid pointer */ );
|
||||
#endif
|
||||
mi_free(p);
|
||||
}
|
||||
|
||||
void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept {
|
||||
MI_UNUSED_RELEASE(alignment);
|
||||
mi_assert(((uintptr_t)p % alignment) == 0);
|
||||
mi_free_size(p,size);
|
||||
}
|
||||
|
||||
void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept {
|
||||
MI_UNUSED_RELEASE(alignment);
|
||||
mi_assert(((uintptr_t)p % alignment) == 0);
|
||||
mi_free(p);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Check for double free in secure and debug mode
|
||||
// This is somewhat expensive so only enabled for secure mode 4
|
||||
// ------------------------------------------------------
|
||||
|
||||
#if MI_CHECK_DOUBLE_FREE
|
||||
// linear check if the free list contains a specific element
|
||||
static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem, const char* list_kind) {
|
||||
const size_t max_count = page->capacity; // can never hold more blocks than the capacity
|
||||
size_t count = 0;
|
||||
while (list != NULL && count <= max_count) { // double-free can create cycles so we limit the number of iterations
|
||||
if (elem==list) return true;
|
||||
list = mi_block_next(page, list);
|
||||
count++;
|
||||
}
|
||||
if mi_unlikely(count > max_count) {
|
||||
_mi_error_message(EFAULT, "corrupted %s list (possibly due to a double free)\n", list_kind);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) {
|
||||
// The decoded value is in the same page (or NULL).
|
||||
// Walk the free lists to verify positively if it is already freed
|
||||
if (mi_list_contains(page, page->free, block, "free") ||
|
||||
mi_list_contains(page, page->local_free, block, "local free") ||
|
||||
mi_list_contains(page, mi_page_thread_free(page), block, "thread free"))
|
||||
{
|
||||
_mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define mi_track_page(page,access) { size_t psize; void* pstart = _mi_page_start(_mi_page_segment(page),page,&psize); mi_track_mem_##access( pstart, psize); }
|
||||
|
||||
// Used for double free checking to avoid checking free lists too frequently
|
||||
static inline bool mi_block_could_be_double_free(const mi_page_t* page, const mi_block_t* block) {
|
||||
mi_block_t* n = mi_block_nextx(page,block,page->keys);
|
||||
return (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer?
|
||||
(n==NULL || mi_is_in_same_page(block,n))); // quick check: in the same page or NULL?
|
||||
}
|
||||
|
||||
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
|
||||
if mi_unlikely(mi_block_could_be_double_free(page,block)) // quick check: next field is aligned in the same page or NULL?
|
||||
{
|
||||
// Suspicious: decoded value a in block is in the same page (or NULL) -- maybe a double free?
|
||||
// (continue in separate function to improve code generation)
|
||||
return mi_check_is_double_freex(page, block);
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
#else
|
||||
static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) {
|
||||
MI_UNUSED(page);
|
||||
MI_UNUSED(block);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Check for heap block overflow by setting up padding at the end of the block
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if MI_PADDING // && !MI_TRACK_ENABLED
|
||||
static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) {
|
||||
*bsize = mi_page_usable_block_size(page);
|
||||
const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize);
|
||||
mi_track_mem_defined(padding,sizeof(mi_padding_t));
|
||||
*delta = padding->delta;
|
||||
uint32_t canary = padding->canary;
|
||||
uintptr_t keys[2];
|
||||
keys[0] = page->keys[0];
|
||||
keys[1] = page->keys[1];
|
||||
bool ok = (mi_ptr_encode_canary(page,block,keys) == canary && *delta <= *bsize);
|
||||
mi_track_mem_noaccess(padding,sizeof(mi_padding_t));
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Return the exact usable size of a block.
|
||||
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) {
|
||||
if (is_guarded) {
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
return (bsize - _mi_os_page_size());
|
||||
}
|
||||
else {
|
||||
size_t bsize;
|
||||
size_t delta;
|
||||
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
|
||||
mi_assert_internal(ok); mi_assert_internal(delta <= bsize);
|
||||
return (ok ? bsize - delta : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// When a non-thread-local block is freed, it becomes part of the thread delayed free
|
||||
// list that is freed later by the owning heap. If the exact usable size is too small to
|
||||
// contain the pointer for the delayed list, then shrink the padding (by decreasing delta)
|
||||
// so it will later not trigger an overflow error in `mi_free_block`.
|
||||
void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
|
||||
size_t bsize;
|
||||
size_t delta;
|
||||
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
|
||||
mi_assert_internal(ok);
|
||||
if (!ok || (bsize - delta) >= min_size) return; // usually already enough space
|
||||
mi_assert_internal(bsize >= min_size);
|
||||
if (bsize < min_size) return; // should never happen
|
||||
size_t new_delta = (bsize - min_size);
|
||||
mi_assert_internal(new_delta < bsize);
|
||||
mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize);
|
||||
mi_track_mem_defined(padding,sizeof(mi_padding_t));
|
||||
padding->delta = (uint32_t)new_delta;
|
||||
mi_track_mem_noaccess(padding,sizeof(mi_padding_t));
|
||||
}
|
||||
#else
|
||||
static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block, bool is_guarded) {
|
||||
MI_UNUSED(block);
|
||||
if (is_guarded) {
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
return (bsize - _mi_os_page_size());
|
||||
}
|
||||
else {
|
||||
return mi_page_usable_block_size(page);
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) {
|
||||
MI_UNUSED(page); MI_UNUSED(block); MI_UNUSED(min_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MI_PADDING && MI_PADDING_CHECK
|
||||
|
||||
static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) {
|
||||
size_t bsize;
|
||||
size_t delta;
|
||||
bool ok = mi_page_decode_padding(page, block, &delta, &bsize);
|
||||
*size = *wrong = bsize;
|
||||
if (!ok) return false;
|
||||
mi_assert_internal(bsize >= delta);
|
||||
*size = bsize - delta;
|
||||
if (!mi_page_is_huge(page)) {
|
||||
uint8_t* fill = (uint8_t*)block + bsize - delta;
|
||||
const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes
|
||||
mi_track_mem_defined(fill, maxpad);
|
||||
for (size_t i = 0; i < maxpad; i++) {
|
||||
if (fill[i] != MI_DEBUG_PADDING) {
|
||||
*wrong = bsize - delta + i;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mi_track_mem_noaccess(fill, maxpad);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
|
||||
size_t size;
|
||||
size_t wrong;
|
||||
if (!mi_verify_padding(page,block,&size,&wrong)) {
|
||||
_mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong );
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) {
|
||||
MI_UNUSED(page);
|
||||
MI_UNUSED(block);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// only maintain stats for smaller objects if requested
|
||||
#if (MI_STAT>0)
|
||||
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
|
||||
MI_UNUSED(block);
|
||||
mi_heap_t* const heap = mi_heap_get_default();
|
||||
const size_t bsize = mi_page_usable_block_size(page);
|
||||
// #if (MI_STAT>1)
|
||||
// const size_t usize = mi_page_usable_size_of(page, block);
|
||||
// mi_heap_stat_decrease(heap, malloc_requested, usize);
|
||||
// #endif
|
||||
if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {
|
||||
mi_heap_stat_decrease(heap, malloc_normal, bsize);
|
||||
#if (MI_STAT > 1)
|
||||
mi_heap_stat_decrease(heap, malloc_bins[_mi_bin(bsize)], 1);
|
||||
#endif
|
||||
}
|
||||
//else if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
// mi_heap_stat_decrease(heap, malloc_large, bsize);
|
||||
//}
|
||||
else {
|
||||
mi_heap_stat_decrease(heap, malloc_huge, bsize);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) {
|
||||
MI_UNUSED(page); MI_UNUSED(block);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Remove guard page when building with MI_GUARDED
|
||||
#if MI_GUARDED
|
||||
static void mi_block_unguard(mi_page_t* page, mi_block_t* block, void* p) {
|
||||
MI_UNUSED(p);
|
||||
mi_assert_internal(mi_block_ptr_is_guarded(block, p));
|
||||
mi_assert_internal(mi_page_has_aligned(page));
|
||||
mi_assert_internal((uint8_t*)p - (uint8_t*)block >= (ptrdiff_t)sizeof(mi_block_t));
|
||||
mi_assert_internal(block->next == MI_BLOCK_TAG_GUARDED);
|
||||
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
const size_t psize = _mi_os_page_size();
|
||||
mi_assert_internal(bsize > psize);
|
||||
mi_assert_internal(_mi_page_segment(page)->allow_decommit);
|
||||
void* gpage = (uint8_t*)block + bsize - psize;
|
||||
mi_assert_internal(_mi_is_aligned(gpage, psize));
|
||||
_mi_os_unprotect(gpage, psize);
|
||||
}
|
||||
#endif
|
||||
+192
-99
@@ -24,7 +24,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
typedef bool (heap_page_visitor_fun)(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2);
|
||||
|
||||
// Visit all pages in a heap; returns `false` if break was called.
|
||||
static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void* arg1, void* arg2)
|
||||
static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, bool include_full, void* arg1, void* arg2)
|
||||
{
|
||||
if (heap==NULL || heap->page_count==0) return true;
|
||||
|
||||
@@ -34,7 +34,8 @@ static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void
|
||||
size_t count = 0;
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i <= MI_BIN_FULL; i++) {
|
||||
const size_t max_bin = (include_full ? MI_BIN_FULL : MI_BIN_FULL - 1);
|
||||
for (size_t i = 0; i <= max_bin; i++) {
|
||||
mi_page_queue_t* pq = &heap->pages[i];
|
||||
mi_page_t* page = pq->first;
|
||||
while(page != NULL) {
|
||||
@@ -47,7 +48,7 @@ static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void
|
||||
page = next; // and continue
|
||||
}
|
||||
}
|
||||
mi_assert_internal(count == total);
|
||||
mi_assert_internal(!include_full || count == total);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ static bool mi_heap_page_is_valid(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
|
||||
MI_UNUSED(pq);
|
||||
mi_assert_internal(mi_page_heap(page) == heap);
|
||||
mi_segment_t* segment = _mi_page_segment(page);
|
||||
mi_assert_internal(segment->thread_id == heap->thread_id);
|
||||
mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id) == heap->thread_id);
|
||||
mi_assert_expensive(_mi_page_is_valid(page));
|
||||
return true;
|
||||
}
|
||||
@@ -67,7 +68,7 @@ static bool mi_heap_page_is_valid(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
|
||||
#if MI_DEBUG>=3
|
||||
static bool mi_heap_is_valid(mi_heap_t* heap) {
|
||||
mi_assert_internal(heap!=NULL);
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_is_valid, NULL, NULL);
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_is_valid, true, NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -95,14 +96,19 @@ static bool mi_heap_page_collect(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t
|
||||
mi_assert_internal(mi_heap_page_is_valid(heap, pq, page, NULL, NULL));
|
||||
mi_collect_t collect = *((mi_collect_t*)arg_collect);
|
||||
_mi_page_free_collect(page, collect >= MI_FORCE);
|
||||
if (collect == MI_FORCE) {
|
||||
// note: call before a potential `_mi_page_free` as the segment may be freed if this was the last used page in that segment.
|
||||
mi_segment_t* segment = _mi_page_segment(page);
|
||||
_mi_segment_collect(segment, true /* force? */);
|
||||
}
|
||||
if (mi_page_all_free(page)) {
|
||||
// no more used blocks, free the page.
|
||||
// note: this will free retired pages as well.
|
||||
bool freed = _PyMem_mi_page_maybe_free(page, pq, collect >= MI_FORCE);
|
||||
if (!freed && collect == MI_ABANDON) {
|
||||
// _PyMem_mi_page_maybe_free may have moved the page to a different
|
||||
// CPython: _PyMem_mi_page_maybe_free may have moved the page to a different
|
||||
// page queue, so we need to re-fetch the correct queue.
|
||||
uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : _mi_bin(page->xblock_size));
|
||||
size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : _mi_bin(mi_page_block_size(page)));
|
||||
_mi_page_abandon(page, &heap->pages[bin]);
|
||||
}
|
||||
}
|
||||
@@ -126,11 +132,11 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
|
||||
{
|
||||
if (heap==NULL || !mi_heap_is_initialized(heap)) return;
|
||||
|
||||
const bool force = collect >= MI_FORCE;
|
||||
const bool force = (collect >= MI_FORCE);
|
||||
_mi_deferred_free(heap, force);
|
||||
|
||||
// gh-112532: we may be called from a thread that is not the owner of the heap
|
||||
bool is_main_thread = _mi_is_main_thread() && heap->thread_id == _mi_thread_id();
|
||||
// python/cpython#112532: we may be called from a thread that is not the owner of the heap
|
||||
const bool is_main_thread = (_mi_is_main_thread() && heap->thread_id == _mi_thread_id());
|
||||
|
||||
// note: never reclaim on collect but leave it to threads that need storage to reclaim
|
||||
const bool force_main =
|
||||
@@ -144,42 +150,47 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
|
||||
if (force_main) {
|
||||
// the main thread is abandoned (end-of-program), try to reclaim all abandoned segments.
|
||||
// if all memory is freed by now, all segments should be freed.
|
||||
// note: this only collects in the current subprocess
|
||||
_mi_abandoned_reclaim_all(heap, &heap->tld->segments);
|
||||
}
|
||||
|
||||
// if abandoning, mark all pages to no longer add to delayed_free
|
||||
if (collect == MI_ABANDON) {
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_never_delayed_free, NULL, NULL);
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_never_delayed_free, true, NULL, NULL);
|
||||
}
|
||||
|
||||
// free all current thread delayed blocks.
|
||||
// (if abandoning, after this there are no more thread-delayed references into the pages.)
|
||||
_mi_heap_delayed_free_all(heap);
|
||||
if (collect >= MI_ABANDON) {
|
||||
_mi_heap_delayed_free_all(heap); // free all
|
||||
} else {
|
||||
_mi_heap_delayed_free_partial(heap); // best-effort; contended blocks are retried next time
|
||||
}
|
||||
|
||||
// collect retired pages
|
||||
_mi_heap_collect_retired(heap, force);
|
||||
|
||||
// free pages that were delayed with QSBR
|
||||
// CPython: free pages that were delayed with QSBR
|
||||
_PyMem_mi_heap_collect_qsbr(heap);
|
||||
|
||||
// collect all pages owned by this thread
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL);
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_collect, (collect!=MI_NORMAL), &collect, NULL); // normally don't visit full pages, issue #1220
|
||||
mi_assert_internal( collect != MI_ABANDON || mi_atomic_load_ptr_acquire(mi_block_t,&heap->thread_delayed_free) == NULL );
|
||||
|
||||
// collect abandoned segments (in particular, purge expired parts of segments in the abandoned segment list)
|
||||
// note: forced purge can be quite expensive if many threads are created/destroyed so we do not force on abandonment
|
||||
_mi_abandoned_collect(heap, collect == MI_FORCE /* force? */, &heap->tld->segments);
|
||||
|
||||
// collect segment local caches
|
||||
if (force) {
|
||||
_mi_segment_thread_collect(&heap->tld->segments);
|
||||
}
|
||||
|
||||
// collect regions on program-exit (or shared library unload)
|
||||
// if forced, collect thread data cache on program-exit (or shared library unload)
|
||||
if (force && is_main_thread && mi_heap_is_backing(heap)) {
|
||||
_mi_thread_data_collect(); // collect thread data cache
|
||||
_mi_arena_collect(true /* force purge */, &heap->tld->stats);
|
||||
}
|
||||
|
||||
// collect arenas (this is program wide so don't force purges on abandonment of threads)
|
||||
_mi_arenas_collect(collect == MI_FORCE /* force purge? */);
|
||||
|
||||
// merge statistics
|
||||
if (collect <= MI_FORCE) { _mi_stats_merge_thread(heap->tld); }
|
||||
}
|
||||
|
||||
void _mi_heap_collect_abandon(mi_heap_t* heap) {
|
||||
@@ -218,39 +229,48 @@ mi_heap_t* mi_heap_get_backing(void) {
|
||||
return bheap;
|
||||
}
|
||||
|
||||
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool no_reclaim, uint8_t tag)
|
||||
{
|
||||
void _mi_heap_init(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool noreclaim, uint8_t tag) {
|
||||
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t));
|
||||
heap->tld = tld;
|
||||
heap->thread_id = _mi_thread_id();
|
||||
heap->arena_id = arena_id;
|
||||
heap->thread_id = _mi_thread_id();
|
||||
heap->arena_id = arena_id;
|
||||
heap->no_reclaim = noreclaim;
|
||||
heap->tag = tag;
|
||||
if (heap == tld->heap_backing) {
|
||||
_mi_random_init(&heap->random);
|
||||
#if defined(_WIN32) && !defined(MI_SHARED_LIB)
|
||||
_mi_random_init_weak(&heap->random); // prevent allocation failure during bcrypt dll initialization with static linking (issue #1185)
|
||||
#else
|
||||
_mi_random_init(&heap->random);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
_mi_random_split(&tld->heap_backing->random, &heap->random);
|
||||
}
|
||||
heap->cookie = _mi_heap_random_next(heap) | 1;
|
||||
heap->cookie = _mi_heap_random_next(heap) | 1;
|
||||
heap->keys[0] = _mi_heap_random_next(heap);
|
||||
heap->keys[1] = _mi_heap_random_next(heap);
|
||||
heap->no_reclaim = no_reclaim;
|
||||
heap->tag = tag;
|
||||
_mi_heap_guarded_init(heap);
|
||||
// push on the thread local heaps list
|
||||
heap->next = heap->tld->heaps;
|
||||
heap->tld->heaps = heap;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
|
||||
mi_decl_nodiscard mi_heap_t* mi_heap_new_ex(int heap_tag, bool allow_destroy, mi_arena_id_t arena_id) {
|
||||
mi_heap_t* bheap = mi_heap_get_backing();
|
||||
mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode?
|
||||
if (heap == NULL) return NULL;
|
||||
// don't reclaim abandoned pages or otherwise destroy is unsafe
|
||||
_mi_heap_init_ex(heap, bheap->tld, arena_id, true, 0);
|
||||
mi_assert(heap_tag >= 0 && heap_tag < 256);
|
||||
_mi_heap_init(heap, bheap->tld, arena_id, allow_destroy /* no reclaim? */, (uint8_t)heap_tag /* heap tag */);
|
||||
return heap;
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
|
||||
return mi_heap_new_ex(0 /* default heap tag */, false /* don't allow `mi_heap_destroy` */, arena_id);
|
||||
}
|
||||
|
||||
mi_decl_nodiscard mi_heap_t* mi_heap_new(void) {
|
||||
return mi_heap_new_in_arena(_mi_arena_id_none());
|
||||
// don't reclaim abandoned memory or otherwise destroy is unsafe
|
||||
return mi_heap_new_ex(0 /* default heap tag */, true /* no reclaim */, _mi_arena_id_none());
|
||||
}
|
||||
|
||||
bool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid) {
|
||||
@@ -303,6 +323,18 @@ static void mi_heap_free(mi_heap_t* heap) {
|
||||
mi_free(heap);
|
||||
}
|
||||
|
||||
// return a heap on the same thread as `heap` specialized for the specified tag (if it exists)
|
||||
mi_heap_t* _mi_heap_by_tag(mi_heap_t* heap, uint8_t tag) {
|
||||
if (heap->tag == tag) {
|
||||
return heap;
|
||||
}
|
||||
for (mi_heap_t *curr = heap->tld->heaps; curr != NULL; curr = curr->next) {
|
||||
if (curr->tag == tag) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Heap destroy
|
||||
@@ -320,24 +352,25 @@ static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
|
||||
// stats
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
if (bsize > MI_MEDIUM_OBJ_SIZE_MAX) {
|
||||
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
mi_heap_stat_decrease(heap, large, bsize);
|
||||
}
|
||||
else {
|
||||
mi_heap_stat_decrease(heap, huge, bsize);
|
||||
//if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
// mi_heap_stat_decrease(heap, malloc_large, bsize);
|
||||
//}
|
||||
//else
|
||||
{
|
||||
mi_heap_stat_decrease(heap, malloc_huge, bsize);
|
||||
}
|
||||
}
|
||||
#if (MI_STAT)
|
||||
#if (MI_STAT>0)
|
||||
_mi_page_free_collect(page, false); // update used count
|
||||
const size_t inuse = page->used;
|
||||
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
mi_heap_stat_decrease(heap, normal, bsize * inuse);
|
||||
#if (MI_STAT>1)
|
||||
mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], inuse);
|
||||
#endif
|
||||
mi_heap_stat_decrease(heap, malloc_normal, bsize * inuse);
|
||||
#if (MI_STAT>1)
|
||||
mi_heap_stat_decrease(heap, malloc_bins[_mi_bin(bsize)], inuse);
|
||||
#endif
|
||||
}
|
||||
mi_heap_stat_decrease(heap, malloc, bsize * inuse); // todo: off for aligned blocks...
|
||||
#endif
|
||||
// mi_heap_stat_decrease(heap, malloc_requested, bsize * inuse); // todo: off for aligned blocks...
|
||||
#endif
|
||||
|
||||
/// pretend it is all free now
|
||||
mi_assert_internal(mi_page_thread_free(page) == NULL);
|
||||
@@ -353,7 +386,7 @@ static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_
|
||||
}
|
||||
|
||||
void _mi_heap_destroy_pages(mi_heap_t* heap) {
|
||||
mi_heap_visit_pages(heap, &_mi_heap_page_destroy, NULL, NULL);
|
||||
mi_heap_visit_pages(heap, &_mi_heap_page_destroy, true, NULL, NULL);
|
||||
mi_heap_reset_pages(heap);
|
||||
}
|
||||
|
||||
@@ -371,7 +404,13 @@ void mi_heap_destroy(mi_heap_t* heap) {
|
||||
mi_assert(heap->no_reclaim);
|
||||
mi_assert_expensive(mi_heap_is_valid(heap));
|
||||
if (heap==NULL || !mi_heap_is_initialized(heap)) return;
|
||||
#if MI_GUARDED
|
||||
// _mi_warning_message("'mi_heap_destroy' called but MI_GUARDED is enabled -- using `mi_heap_delete` instead (heap at %p)\n", heap);
|
||||
mi_heap_delete(heap);
|
||||
return;
|
||||
#else
|
||||
if (!heap->no_reclaim) {
|
||||
_mi_warning_message("'mi_heap_destroy' called but ignored as the heap was not created with 'allow_destroy' (heap at %p)\n", heap);
|
||||
// don't free in case it may contain reclaimed pages
|
||||
mi_heap_delete(heap);
|
||||
}
|
||||
@@ -384,12 +423,14 @@ void mi_heap_destroy(mi_heap_t* heap) {
|
||||
_mi_heap_destroy_pages(heap);
|
||||
mi_heap_free(heap);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// forcefully destroy all heaps in the current thread
|
||||
void _mi_heap_unsafe_destroy_all(void) {
|
||||
mi_heap_t* bheap = mi_heap_get_backing();
|
||||
mi_heap_t* curr = bheap->tld->heaps;
|
||||
void _mi_heap_unsafe_destroy_all(mi_heap_t* heap) {
|
||||
mi_assert_internal(heap != NULL);
|
||||
if (heap == NULL) return;
|
||||
mi_heap_t* curr = heap->tld->heaps;
|
||||
while (curr != NULL) {
|
||||
mi_heap_t* next = curr->next;
|
||||
if (curr->no_reclaim) {
|
||||
@@ -440,6 +481,12 @@ static void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) {
|
||||
mi_heap_reset_pages(from);
|
||||
}
|
||||
|
||||
// are two heaps compatible with respect to heap-tag, exclusive arena etc.
|
||||
static bool mi_heaps_are_compatible(mi_heap_t* heap1, mi_heap_t* heap2) {
|
||||
return (heap1->tag == heap2->tag && // store same kind of objects
|
||||
heap1->arena_id == heap2->arena_id); // same arena preference
|
||||
}
|
||||
|
||||
// Safe delete a heap without freeing any still allocated blocks in that heap.
|
||||
void mi_heap_delete(mi_heap_t* heap)
|
||||
{
|
||||
@@ -448,9 +495,10 @@ void mi_heap_delete(mi_heap_t* heap)
|
||||
mi_assert_expensive(mi_heap_is_valid(heap));
|
||||
if (heap==NULL || !mi_heap_is_initialized(heap)) return;
|
||||
|
||||
if (!mi_heap_is_backing(heap)) {
|
||||
mi_heap_t* bheap = heap->tld->heap_backing;
|
||||
if (bheap != heap && mi_heaps_are_compatible(bheap,heap)) {
|
||||
// transfer still used pages to the backing heap
|
||||
mi_heap_absorb(heap->tld->heap_backing, heap);
|
||||
mi_heap_absorb(bheap, heap);
|
||||
}
|
||||
else {
|
||||
// the backing heap abandons its pages
|
||||
@@ -498,8 +546,7 @@ static bool mi_heap_page_check_owned(mi_heap_t* heap, mi_page_queue_t* pq, mi_pa
|
||||
MI_UNUSED(heap);
|
||||
MI_UNUSED(pq);
|
||||
bool* found = (bool*)vfound;
|
||||
mi_segment_t* segment = _mi_page_segment(page);
|
||||
void* start = _mi_page_start(segment, page, NULL);
|
||||
void* start = mi_page_start(page);
|
||||
void* end = (uint8_t*)start + (page->capacity * mi_page_block_size(page));
|
||||
*found = (p >= start && p < end);
|
||||
return (!*found); // continue if not found
|
||||
@@ -510,7 +557,7 @@ bool mi_heap_check_owned(mi_heap_t* heap, const void* p) {
|
||||
if (heap==NULL || !mi_heap_is_initialized(heap)) return false;
|
||||
if (((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0) return false; // only aligned pointers
|
||||
bool found = false;
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_check_owned, (void*)p, &found);
|
||||
mi_heap_visit_pages(heap, &mi_heap_page_check_owned, true, (void*)p, &found);
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -524,50 +571,61 @@ bool mi_check_owned(const void* p) {
|
||||
enable visiting all blocks of all heaps across threads
|
||||
----------------------------------------------------------- */
|
||||
|
||||
// Separate struct to keep `mi_page_t` out of the public interface
|
||||
typedef struct mi_heap_area_ex_s {
|
||||
mi_heap_area_t area;
|
||||
mi_page_t* page;
|
||||
} mi_heap_area_ex_t;
|
||||
void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) {
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
const size_t ubsize = mi_page_usable_block_size(page);
|
||||
area->reserved = page->reserved * bsize;
|
||||
area->committed = page->capacity * bsize;
|
||||
area->blocks = mi_page_start(page);
|
||||
area->used = page->used; // number of blocks in use (#553)
|
||||
area->block_size = ubsize;
|
||||
area->full_block_size = bsize;
|
||||
area->heap_tag = page->heap_tag;
|
||||
}
|
||||
|
||||
static void mi_fast_divisor(size_t divisor, size_t* magic, size_t* shift) {
|
||||
|
||||
static void mi_get_fast_divisor(size_t divisor, uint64_t* magic, size_t* shift) {
|
||||
mi_assert_internal(divisor > 0 && divisor <= UINT32_MAX);
|
||||
*shift = MI_INTPTR_BITS - mi_clz(divisor - 1);
|
||||
*magic = (size_t)(((1ULL << 32) * ((1ULL << *shift) - divisor)) / divisor + 1);
|
||||
*shift = MI_SIZE_BITS - mi_clz(divisor - 1);
|
||||
*magic = ((((uint64_t)1 << 32) * (((uint64_t)1 << *shift) - divisor)) / divisor + 1);
|
||||
}
|
||||
|
||||
static size_t mi_fast_divide(size_t n, size_t magic, size_t shift) {
|
||||
static size_t mi_fast_divide(size_t n, uint64_t magic, size_t shift) {
|
||||
mi_assert_internal(n <= UINT32_MAX);
|
||||
return ((((uint64_t) n * magic) >> 32) + n) >> shift;
|
||||
const uint64_t hi = ((uint64_t)n * magic) >> 32;
|
||||
return (size_t)((hi + n) >> shift);
|
||||
}
|
||||
|
||||
bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_block_visit_fun* visitor, void* arg) {
|
||||
bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi_block_visit_fun* visitor, void* arg) {
|
||||
mi_assert(area != NULL);
|
||||
if (area==NULL) return true;
|
||||
mi_assert(page != NULL);
|
||||
if (page == NULL) return true;
|
||||
|
||||
// collect early so the page used count is reported correctly
|
||||
// _mi_page_free_collect(page,true); // collect both thread_delayed and local_free
|
||||
mi_assert_internal(page->local_free == NULL);
|
||||
if (page->used == 0) return true;
|
||||
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
const size_t ubsize = mi_page_usable_block_size(page); // without padding
|
||||
size_t psize;
|
||||
uint8_t* pstart = _mi_page_start(_mi_page_segment(page), page, &psize);
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
size_t psize;
|
||||
uint8_t* const pstart = _mi_segment_page_start(_mi_page_segment(page), page, &psize);
|
||||
mi_heap_t* const heap = mi_page_heap(page);
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
const size_t ubsize = mi_page_usable_block_size(page); // without padding
|
||||
|
||||
// optimize page with one block
|
||||
if (page->capacity == 1) {
|
||||
// optimize page with one block
|
||||
mi_assert_internal(page->used == 1 && page->free == NULL);
|
||||
return visitor(heap, area, pstart, ubsize, arg);
|
||||
return visitor(mi_page_heap(page), area, pstart, ubsize, arg);
|
||||
}
|
||||
mi_assert(bsize <= UINT32_MAX);
|
||||
|
||||
// optimize full pages
|
||||
if (page->used == page->capacity) {
|
||||
// optimize full pages
|
||||
uint8_t* block = pstart;
|
||||
for (size_t i = 0; i < page->capacity; i++) {
|
||||
if (!visitor(heap, area, block, ubsize, arg)) return false;
|
||||
block += bsize;
|
||||
if (!visitor(heap, area, block, ubsize, arg)) return false;
|
||||
block += bsize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -575,29 +633,31 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_
|
||||
// create a bitmap of free blocks.
|
||||
#define MI_MAX_BLOCKS (MI_SMALL_PAGE_SIZE / sizeof(void*))
|
||||
uintptr_t free_map[MI_MAX_BLOCKS / MI_INTPTR_BITS];
|
||||
size_t bmapsize = (page->capacity + MI_INTPTR_BITS - 1) / MI_INTPTR_BITS;
|
||||
memset(free_map, 0, bmapsize * sizeof(uintptr_t));
|
||||
|
||||
const uintptr_t bmapsize = _mi_divide_up(page->capacity, MI_INTPTR_BITS);
|
||||
memset(free_map, 0, bmapsize * sizeof(intptr_t));
|
||||
if (page->capacity % MI_INTPTR_BITS != 0) {
|
||||
size_t shift = (page->capacity % MI_INTPTR_BITS);
|
||||
// mark left-over bits at the end as free
|
||||
size_t shift = (page->capacity % MI_INTPTR_BITS);
|
||||
uintptr_t mask = (UINTPTR_MAX << shift);
|
||||
free_map[bmapsize-1] = mask;
|
||||
free_map[bmapsize - 1] = mask;
|
||||
}
|
||||
|
||||
// fast repeated division by the block size
|
||||
size_t magic, shift;
|
||||
mi_fast_divisor(bsize, &magic, &shift);
|
||||
uint64_t magic;
|
||||
size_t shift;
|
||||
mi_get_fast_divisor(bsize, &magic, &shift);
|
||||
|
||||
#if MI_DEBUG>1
|
||||
size_t free_count = 0;
|
||||
#endif
|
||||
for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) {
|
||||
for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {
|
||||
#if MI_DEBUG>1
|
||||
free_count++;
|
||||
#endif
|
||||
mi_assert_internal((uint8_t*)block >= pstart && (uint8_t*)block < (pstart + psize));
|
||||
size_t offset = (uint8_t*)block - pstart;
|
||||
mi_assert_internal(offset % bsize == 0);
|
||||
mi_assert_internal(offset <= UINT32_MAX);
|
||||
size_t blockidx = mi_fast_divide(offset, magic, shift);
|
||||
mi_assert_internal(blockidx == offset / bsize);
|
||||
mi_assert_internal(blockidx < MI_MAX_BLOCKS);
|
||||
@@ -624,14 +684,15 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_
|
||||
}
|
||||
}
|
||||
else {
|
||||
// visit the used blocks in the mask
|
||||
uintptr_t m = ~free_map[i];
|
||||
while (m) {
|
||||
while (m != 0) {
|
||||
#if MI_DEBUG>1
|
||||
used_count++;
|
||||
#endif
|
||||
size_t bitidx = mi_ctz(m);
|
||||
if (!visitor(heap, area, block + (bitidx * bsize), ubsize, arg)) return false;
|
||||
m &= m - 1;
|
||||
m &= m - 1; // clear least significant bit
|
||||
}
|
||||
block += bsize * MI_INTPTR_BITS;
|
||||
}
|
||||
@@ -640,26 +701,24 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg);
|
||||
|
||||
void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) {
|
||||
_mi_page_free_collect(page,true);
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
const size_t ubsize = mi_page_usable_block_size(page);
|
||||
area->reserved = page->reserved * bsize;
|
||||
area->committed = page->capacity * bsize;
|
||||
area->blocks = _mi_page_start(_mi_page_segment(page), page, NULL);
|
||||
area->used = page->used; // number of blocks in use (#553)
|
||||
area->block_size = ubsize;
|
||||
area->full_block_size = bsize;
|
||||
}
|
||||
|
||||
// Separate struct to keep `mi_page_t` out of the public interface
|
||||
typedef struct mi_heap_area_ex_s {
|
||||
mi_heap_area_t area;
|
||||
mi_page_t* page;
|
||||
} mi_heap_area_ex_t;
|
||||
|
||||
typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg);
|
||||
|
||||
static bool mi_heap_visit_areas_page(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* vfun, void* arg) {
|
||||
MI_UNUSED(heap);
|
||||
MI_UNUSED(pq);
|
||||
if (page==NULL) return true;
|
||||
mi_heap_area_visit_fun* fun = (mi_heap_area_visit_fun*)vfun;
|
||||
mi_heap_area_ex_t xarea;
|
||||
xarea.page = page;
|
||||
_mi_page_free_collect(page,true); // collect early so the page->used is accurate
|
||||
_mi_heap_area_init(&xarea.area, page);
|
||||
return fun(heap, &xarea, arg);
|
||||
}
|
||||
@@ -667,7 +726,7 @@ static bool mi_heap_visit_areas_page(mi_heap_t* heap, mi_page_queue_t* pq, mi_pa
|
||||
// Visit all heap pages as areas
|
||||
static bool mi_heap_visit_areas(const mi_heap_t* heap, mi_heap_area_visit_fun* visitor, void* arg) {
|
||||
if (visitor == NULL) return false;
|
||||
return mi_heap_visit_pages((mi_heap_t*)heap, &mi_heap_visit_areas_page, (void*)(visitor), arg); // note: function pointer to void* :-{
|
||||
return mi_heap_visit_pages((mi_heap_t*)heap, &mi_heap_visit_areas_page, true, (void*)(visitor), arg); // note: function pointer to void* :-{
|
||||
}
|
||||
|
||||
// Just to pass arguments
|
||||
@@ -691,6 +750,40 @@ static bool mi_heap_area_visitor(const mi_heap_t* heap, const mi_heap_area_ex_t*
|
||||
// Visit all blocks in a heap
|
||||
bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {
|
||||
mi_visit_blocks_args_t args = { visit_blocks, visitor, arg };
|
||||
_mi_heap_delayed_free_partial((mi_heap_t *)heap);
|
||||
_mi_heap_delayed_free_partial((mi_heap_t *)heap); // CPython: gh-112532
|
||||
return mi_heap_visit_areas(heap, &mi_heap_area_visitor, &args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const mi_page_t* mi_safe_ptr_page(void* p) {
|
||||
const mi_segment_t* const segment = _mi_ptr_segment(p);
|
||||
if mi_unlikely(segment==NULL) return NULL;
|
||||
#ifndef NDEBUG
|
||||
if mi_unlikely(!mi_is_in_heap_region(p)) return NULL;
|
||||
#endif
|
||||
if mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie) return NULL;
|
||||
return _mi_segment_page_of(segment, p);
|
||||
}
|
||||
|
||||
// unsafe heap utilization function for DragonFly (see issue #1258)
|
||||
// If the page of pointer `p` belongs to `heap` (or `heap==NULL`) and has less than `perc_threshold` used blocks in its used area return `true`.
|
||||
// This function is unsafe in general as it assumes we are the only thread accessing the page of `p`.
|
||||
bool mi_unsafe_heap_page_is_under_utilized(mi_heap_t* heap, void* p, size_t perc_threshold) mi_attr_noexcept {
|
||||
if (p==NULL) return false;
|
||||
const mi_page_t* const page = mi_safe_ptr_page(p); // Get the page containing this pointer
|
||||
if (page==NULL || page->used==page->capacity || page->capacity < page->reserved) return false;
|
||||
// If the page is the head of the queue, it is currently being used for
|
||||
// allocations; we skip it to avoid immediate thrashing.
|
||||
if (page->prev == NULL) return false;
|
||||
|
||||
// match heap?
|
||||
const mi_heap_t* const page_heap = mi_page_heap(page);
|
||||
if (page_heap==NULL) return false;
|
||||
if (heap!=NULL && page_heap!=heap) return false;
|
||||
|
||||
// check utilization
|
||||
if (page->capacity==0) return false;
|
||||
if (perc_threshold>=100) return true;
|
||||
return (perc_threshold >= ((100UL*page->used) / page->capacity));
|
||||
}
|
||||
+230
-200
@@ -13,7 +13,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
|
||||
|
||||
// Empty page used to initialize the small free pages array
|
||||
const mi_page_t _mi_page_empty;
|
||||
const mi_page_t _mi_page_empty; // CPython: zero-initialized since mi_page_t has extra fields
|
||||
|
||||
#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty)
|
||||
|
||||
@@ -45,27 +45,26 @@ const mi_page_t _mi_page_empty;
|
||||
QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \
|
||||
QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 2) /* Full queue */ }
|
||||
|
||||
#define MI_STAT_COUNT_NULL() {0,0,0,0}
|
||||
#define MI_STAT_COUNT_NULL() {0,0,0}
|
||||
|
||||
// Empty statistics
|
||||
#if MI_STAT>1
|
||||
#define MI_STAT_COUNT_END_NULL() , { MI_STAT_COUNT_NULL(), MI_INIT32(MI_STAT_COUNT_NULL) }
|
||||
#else
|
||||
#define MI_STAT_COUNT_END_NULL()
|
||||
#endif
|
||||
|
||||
#define MI_STATS_NULL \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), \
|
||||
{ 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \
|
||||
{ 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } \
|
||||
MI_STAT_COUNT_END_NULL()
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
{ 0 }, { 0 }, \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \
|
||||
{ 0 }, { 0 }, { 0 }, { 0 }, \
|
||||
{ 0 }, { 0 }, { 0 }, { 0 }, \
|
||||
\
|
||||
{ 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, \
|
||||
MI_INIT4(MI_STAT_COUNT_NULL), \
|
||||
{ 0 }, { 0 }, { 0 }, { 0 }, \
|
||||
\
|
||||
{ MI_INIT4(MI_STAT_COUNT_NULL) }, \
|
||||
{ { 0 }, { 0 }, { 0 }, { 0 } }, \
|
||||
\
|
||||
{ MI_INIT74(MI_STAT_COUNT_NULL) }, \
|
||||
{ MI_INIT74(MI_STAT_COUNT_NULL) }
|
||||
|
||||
|
||||
// Empty slice span queues for every bin
|
||||
@@ -90,8 +89,6 @@ const mi_page_t _mi_page_empty;
|
||||
|
||||
mi_decl_cache_align const mi_heap_t _mi_heap_empty = {
|
||||
NULL,
|
||||
MI_SMALL_PAGES_EMPTY,
|
||||
MI_PAGE_QUEUES_EMPTY,
|
||||
MI_ATOMIC_VAR_INIT(NULL),
|
||||
0, // tid
|
||||
0, // cookie
|
||||
@@ -100,45 +97,52 @@ mi_decl_cache_align const mi_heap_t _mi_heap_empty = {
|
||||
{ {0}, {0}, 0, true }, // random
|
||||
0, // page count
|
||||
MI_BIN_FULL, 0, // page retired min/max
|
||||
0, // pages_full_size
|
||||
0, 0, // generic count
|
||||
NULL, // next
|
||||
false,
|
||||
0,
|
||||
0
|
||||
false, // can reclaim
|
||||
0, // tag
|
||||
0, // debug_offset (CPython)
|
||||
false, // page_use_qsbr (CPython)
|
||||
#if MI_GUARDED
|
||||
0, 0, 0, 1, // rate is 0 and count is 1 so we never write to it (see `internal.h:mi_heap_malloc_use_guarded`)
|
||||
#endif
|
||||
MI_SMALL_PAGES_EMPTY,
|
||||
MI_PAGE_QUEUES_EMPTY
|
||||
};
|
||||
|
||||
static mi_decl_cache_align mi_subproc_t mi_subproc_default;
|
||||
|
||||
#define tld_empty_stats ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats)))
|
||||
#define tld_empty_os ((mi_os_tld_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,os)))
|
||||
|
||||
mi_decl_cache_align static const mi_tld_t tld_empty = {
|
||||
0,
|
||||
false,
|
||||
NULL, NULL,
|
||||
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, tld_empty_stats, tld_empty_os, &_mi_abandoned_default }, // segments
|
||||
{ 0, tld_empty_stats }, // os
|
||||
{ MI_STATS_NULL } // stats
|
||||
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, tld_empty_stats }, // segments
|
||||
{ sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL } // stats
|
||||
};
|
||||
|
||||
mi_threadid_t _mi_thread_id(void) mi_attr_noexcept {
|
||||
return _mi_prim_thread_id();
|
||||
mi_threadid_t tid = _mi_prim_thread_id();
|
||||
mi_assert_internal( (tid & 0x03) == 0 ); // mimalloc reserves the bottom 2 bits
|
||||
return tid;
|
||||
}
|
||||
|
||||
// the thread-local default heap for allocation
|
||||
mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty;
|
||||
|
||||
extern mi_heap_t _mi_heap_main;
|
||||
extern mi_decl_hidden mi_heap_t _mi_heap_main;
|
||||
|
||||
static mi_tld_t tld_main = {
|
||||
static mi_decl_cache_align mi_tld_t tld_main = {
|
||||
0, false,
|
||||
&_mi_heap_main, & _mi_heap_main,
|
||||
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, &tld_main.stats, &tld_main.os, &_mi_abandoned_default }, // segments
|
||||
{ 0, &tld_main.stats }, // os
|
||||
{ MI_STATS_NULL } // stats
|
||||
{ MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, &tld_main.stats }, // segments
|
||||
{ sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL } // stats
|
||||
};
|
||||
|
||||
mi_heap_t _mi_heap_main = {
|
||||
mi_decl_cache_align mi_heap_t _mi_heap_main = {
|
||||
&tld_main,
|
||||
MI_SMALL_PAGES_EMPTY,
|
||||
MI_PAGE_QUEUES_EMPTY,
|
||||
MI_ATOMIC_VAR_INIT(NULL),
|
||||
0, // thread id
|
||||
0, // initial cookie
|
||||
@@ -147,13 +151,61 @@ mi_heap_t _mi_heap_main = {
|
||||
{ {0x846ca68b}, {0}, 0, true }, // random
|
||||
0, // page count
|
||||
MI_BIN_FULL, 0, // page retired min/max
|
||||
0, // pages_full_size
|
||||
0, 0, // generic count
|
||||
NULL, // next heap
|
||||
false // can reclaim
|
||||
false, // can reclaim
|
||||
0, // tag
|
||||
0, // debug_offset (CPython)
|
||||
false, // page_use_qsbr (CPython)
|
||||
#if MI_GUARDED
|
||||
0, 0, 0, 0,
|
||||
#endif
|
||||
MI_SMALL_PAGES_EMPTY,
|
||||
MI_PAGE_QUEUES_EMPTY
|
||||
};
|
||||
|
||||
bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`.
|
||||
|
||||
mi_stats_t _mi_stats_main = { MI_STATS_NULL };
|
||||
mi_stats_t _mi_stats_main = { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL };
|
||||
|
||||
#if MI_GUARDED
|
||||
mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {
|
||||
heap->guarded_sample_rate = sample_rate;
|
||||
heap->guarded_sample_count = sample_rate; // count down samples
|
||||
if (heap->guarded_sample_rate > 1) {
|
||||
if (seed == 0) {
|
||||
seed = _mi_heap_random_next(heap);
|
||||
}
|
||||
heap->guarded_sample_count = (seed % heap->guarded_sample_rate) + 1; // start at random count between 1 and `sample_rate`
|
||||
}
|
||||
}
|
||||
|
||||
mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) {
|
||||
heap->guarded_size_min = min;
|
||||
heap->guarded_size_max = (min > max ? min : max);
|
||||
}
|
||||
|
||||
void _mi_heap_guarded_init(mi_heap_t* heap) {
|
||||
mi_heap_guarded_set_sample_rate(heap,
|
||||
(size_t)mi_option_get_clamp(mi_option_guarded_sample_rate, 0, LONG_MAX),
|
||||
(size_t)mi_option_get(mi_option_guarded_sample_seed));
|
||||
mi_heap_guarded_set_size_bound(heap,
|
||||
(size_t)mi_option_get_clamp(mi_option_guarded_min, 0, LONG_MAX),
|
||||
(size_t)mi_option_get_clamp(mi_option_guarded_max, 0, LONG_MAX) );
|
||||
}
|
||||
#else
|
||||
mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {
|
||||
MI_UNUSED(heap); MI_UNUSED(sample_rate); MI_UNUSED(seed);
|
||||
}
|
||||
|
||||
mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) {
|
||||
MI_UNUSED(heap); MI_UNUSED(min); MI_UNUSED(max);
|
||||
}
|
||||
void _mi_heap_guarded_init(mi_heap_t* heap) {
|
||||
MI_UNUSED(heap);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void mi_heap_main_init(void) {
|
||||
@@ -168,6 +220,9 @@ static void mi_heap_main_init(void) {
|
||||
_mi_heap_main.cookie = _mi_heap_random_next(&_mi_heap_main);
|
||||
_mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main);
|
||||
_mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main);
|
||||
mi_lock_init(&mi_subproc_default.abandoned_os_lock);
|
||||
mi_lock_init(&mi_subproc_default.abandoned_os_visit_lock);
|
||||
_mi_heap_guarded_init(&_mi_heap_main);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +231,57 @@ mi_heap_t* _mi_heap_main_get(void) {
|
||||
return &_mi_heap_main;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Sub process
|
||||
----------------------------------------------------------- */
|
||||
|
||||
mi_subproc_id_t mi_subproc_main(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mi_subproc_id_t mi_subproc_new(void) {
|
||||
mi_memid_t memid = _mi_memid_none();
|
||||
mi_subproc_t* subproc = (mi_subproc_t*)_mi_arena_meta_zalloc(sizeof(mi_subproc_t), &memid);
|
||||
if (subproc == NULL) return NULL;
|
||||
subproc->memid = memid;
|
||||
subproc->abandoned_os_list = NULL;
|
||||
mi_lock_init(&subproc->abandoned_os_lock);
|
||||
mi_lock_init(&subproc->abandoned_os_visit_lock);
|
||||
return subproc;
|
||||
}
|
||||
|
||||
mi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) {
|
||||
return (subproc_id == NULL ? &mi_subproc_default : (mi_subproc_t*)subproc_id);
|
||||
}
|
||||
|
||||
void mi_subproc_delete(mi_subproc_id_t subproc_id) {
|
||||
if (subproc_id == NULL) return;
|
||||
mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id);
|
||||
if (subproc==NULL) return;
|
||||
// check if there are no abandoned segments still..
|
||||
bool safe_to_delete = false;
|
||||
mi_lock(&subproc->abandoned_os_lock) {
|
||||
if (subproc->abandoned_os_list == NULL) {
|
||||
safe_to_delete = true;
|
||||
}
|
||||
}
|
||||
if (!safe_to_delete) return;
|
||||
// safe to release
|
||||
// todo: should we refcount subprocesses?
|
||||
mi_lock_done(&subproc->abandoned_os_lock);
|
||||
mi_lock_done(&subproc->abandoned_os_visit_lock);
|
||||
_mi_arena_meta_free(subproc, subproc->memid, sizeof(mi_subproc_t));
|
||||
}
|
||||
|
||||
void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) {
|
||||
mi_heap_t* heap = mi_heap_get_default();
|
||||
if (heap == NULL) return;
|
||||
mi_assert(heap->tld->segments.subproc == &mi_subproc_default);
|
||||
if (heap->tld->segments.subproc != &mi_subproc_default) return;
|
||||
heap->tld->segments.subproc = _mi_subproc_from_id(subproc_id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Initialization and freeing of the thread local heaps
|
||||
@@ -194,12 +300,11 @@ typedef struct mi_thread_data_s {
|
||||
// destroy many OS threads, this may causes too much overhead
|
||||
// per thread so we maintain a small cache of recently freed metadata.
|
||||
|
||||
#define TD_CACHE_SIZE (16)
|
||||
#define TD_CACHE_SIZE (32)
|
||||
static _Atomic(mi_thread_data_t*) td_cache[TD_CACHE_SIZE];
|
||||
|
||||
static mi_thread_data_t* mi_thread_data_zalloc(void) {
|
||||
// try to find thread metadata in the cache
|
||||
bool is_zero = false;
|
||||
mi_thread_data_t* td = NULL;
|
||||
for (int i = 0; i < TD_CACHE_SIZE; i++) {
|
||||
td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]);
|
||||
@@ -207,32 +312,25 @@ static mi_thread_data_t* mi_thread_data_zalloc(void) {
|
||||
// found cached allocation, try use it
|
||||
td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL);
|
||||
if (td != NULL) {
|
||||
break;
|
||||
_mi_memzero(td, offsetof(mi_thread_data_t,memid));
|
||||
return td;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if that fails, allocate as meta data
|
||||
mi_memid_t memid;
|
||||
td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid);
|
||||
if (td == NULL) {
|
||||
mi_memid_t memid;
|
||||
td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &memid, &_mi_stats_main);
|
||||
// if this fails, try once more. (issue #257)
|
||||
td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid);
|
||||
if (td == NULL) {
|
||||
// if this fails, try once more. (issue #257)
|
||||
td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &memid, &_mi_stats_main);
|
||||
if (td == NULL) {
|
||||
// really out of memory
|
||||
_mi_error_message(ENOMEM, "unable to allocate thread local heap metadata (%zu bytes)\n", sizeof(mi_thread_data_t));
|
||||
}
|
||||
}
|
||||
if (td != NULL) {
|
||||
td->memid = memid;
|
||||
is_zero = memid.initially_zero;
|
||||
// really out of memory
|
||||
_mi_error_message(ENOMEM, "unable to allocate thread local heap metadata (%zu bytes)\n", sizeof(mi_thread_data_t));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (td != NULL && !is_zero) {
|
||||
_mi_memzero_aligned(td, offsetof(mi_thread_data_t,memid));
|
||||
}
|
||||
td->memid = memid;
|
||||
return td;
|
||||
}
|
||||
|
||||
@@ -248,7 +346,7 @@ static void mi_thread_data_free( mi_thread_data_t* tdfree ) {
|
||||
}
|
||||
}
|
||||
// if that fails, just free it directly
|
||||
_mi_os_free(tdfree, sizeof(mi_thread_data_t), tdfree->memid, &_mi_stats_main);
|
||||
_mi_os_free(tdfree, sizeof(mi_thread_data_t), tdfree->memid);
|
||||
}
|
||||
|
||||
void _mi_thread_data_collect(void) {
|
||||
@@ -258,14 +356,14 @@ void _mi_thread_data_collect(void) {
|
||||
if (td != NULL) {
|
||||
td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL);
|
||||
if (td != NULL) {
|
||||
_mi_os_free(td, sizeof(mi_thread_data_t), td->memid, &_mi_stats_main);
|
||||
_mi_os_free(td, sizeof(mi_thread_data_t), td->memid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the thread local default heap, called from `mi_thread_init`
|
||||
static bool _mi_heap_init(void) {
|
||||
static bool _mi_thread_heap_init(void) {
|
||||
if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true;
|
||||
if (_mi_is_main_thread()) {
|
||||
// mi_assert_internal(_mi_heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization
|
||||
@@ -279,24 +377,26 @@ static bool _mi_heap_init(void) {
|
||||
mi_thread_data_t* td = mi_thread_data_zalloc();
|
||||
if (td == NULL) return false;
|
||||
|
||||
_mi_tld_init(&td->tld, &td->heap);
|
||||
_mi_heap_init_ex(&td->heap, &td->tld, _mi_arena_id_none(), false, 0);
|
||||
_mi_heap_set_default_direct(&td->heap);
|
||||
mi_tld_t* tld = &td->tld;
|
||||
mi_heap_t* heap = &td->heap;
|
||||
_mi_tld_init(tld, heap); // must be before `_mi_heap_init`
|
||||
_mi_heap_init(heap, tld, _mi_arena_id_none(), false /* can reclaim */, 0 /* default tag */);
|
||||
_mi_heap_set_default_direct(heap);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// initialize thread local data
|
||||
void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) {
|
||||
_mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld));
|
||||
tld->segments.stats = &tld->stats;
|
||||
tld->segments.os = &tld->os;
|
||||
tld->segments.abandoned = &_mi_abandoned_default;
|
||||
tld->os.stats = &tld->stats;
|
||||
tld->heap_backing = bheap;
|
||||
_mi_memcpy_aligned(tld, &tld_empty, sizeof(mi_tld_t));
|
||||
tld->heap_backing = bheap;
|
||||
tld->heaps = NULL;
|
||||
tld->segments.subproc = &mi_subproc_default;
|
||||
tld->segments.stats = &tld->stats;
|
||||
}
|
||||
|
||||
// Free the thread local default heap (called from `mi_thread_done`)
|
||||
static bool _mi_heap_done(mi_heap_t* heap) {
|
||||
static bool _mi_thread_heap_done(mi_heap_t* heap) {
|
||||
if (!mi_heap_is_initialized(heap)) return true;
|
||||
|
||||
// reset default heap
|
||||
@@ -330,7 +430,7 @@ static bool _mi_heap_done(mi_heap_t* heap) {
|
||||
// free if not the main thread
|
||||
if (heap != &_mi_heap_main) {
|
||||
// the following assertion does not always hold for huge segments as those are always treated
|
||||
// as abondened: one may allocate it in one thread, but deallocate in another in which case
|
||||
// as abandoned: one may allocate it in one thread, but deallocate in another in which case
|
||||
// the count can be too large or negative. todo: perhaps not count huge segments? see issue #363
|
||||
// mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id());
|
||||
mi_thread_data_free((mi_thread_data_t*)heap);
|
||||
@@ -366,11 +466,10 @@ static bool _mi_heap_done(mi_heap_t* heap) {
|
||||
|
||||
// Set up handlers so `mi_thread_done` is called automatically
|
||||
static void mi_process_setup_auto_thread_done(void) {
|
||||
static bool tls_initialized = false; // fine if it races
|
||||
if (tls_initialized) return;
|
||||
tls_initialized = true;
|
||||
_mi_prim_thread_init_auto_done();
|
||||
_mi_heap_set_default_direct(&_mi_heap_main);
|
||||
mi_atomic_do_once {
|
||||
_mi_prim_thread_init_auto_done();
|
||||
_mi_heap_set_default_direct(&_mi_heap_main);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -393,7 +492,7 @@ void mi_thread_init(void) mi_attr_noexcept
|
||||
// initialize the thread local default heap
|
||||
// (this will call `_mi_heap_set_default_direct` and thus set the
|
||||
// fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called)
|
||||
if (_mi_heap_init()) return; // returns true if already initialized
|
||||
if (_mi_thread_heap_init()) return; // returns true if already initialized
|
||||
|
||||
_mi_stat_increase(&_mi_stats_main.threads, 1);
|
||||
mi_atomic_increment_relaxed(&thread_count);
|
||||
@@ -425,7 +524,7 @@ void _mi_thread_done(mi_heap_t* heap)
|
||||
if (heap->thread_id != _mi_thread_id()) return;
|
||||
|
||||
// abandon the thread local heap
|
||||
if (_mi_heap_done(heap)) return; // returns true if already ran
|
||||
if (_mi_thread_heap_done(heap)) return; // returns true if already ran
|
||||
}
|
||||
|
||||
void _mi_heap_set_default_direct(mi_heap_t* heap) {
|
||||
@@ -433,7 +532,7 @@ void _mi_heap_set_default_direct(mi_heap_t* heap) {
|
||||
#if defined(MI_TLS_SLOT)
|
||||
mi_prim_tls_slot_set(MI_TLS_SLOT,heap);
|
||||
#elif defined(MI_TLS_PTHREAD_SLOT_OFS)
|
||||
*mi_tls_pthread_heap_slot() = heap;
|
||||
*mi_prim_tls_pthread_heap_slot() = heap;
|
||||
#elif defined(MI_TLS_PTHREAD)
|
||||
// we use _mi_heap_default_key
|
||||
#else
|
||||
@@ -445,58 +544,27 @@ void _mi_heap_set_default_direct(mi_heap_t* heap) {
|
||||
_mi_prim_thread_associate_default_heap(heap);
|
||||
}
|
||||
|
||||
void mi_thread_set_in_threadpool(void) mi_attr_noexcept {
|
||||
// nothing
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Run functions on process init/done, and thread init/done
|
||||
// --------------------------------------------------------
|
||||
static void mi_cdecl mi_process_done(void);
|
||||
|
||||
static bool os_preloading = true; // true until this module is initialized
|
||||
static bool mi_redirected = false; // true if malloc redirects to mi_malloc
|
||||
|
||||
// Returns true if this module has not been initialized; Don't use C runtime routines until it returns false.
|
||||
bool mi_decl_noinline _mi_preloading(void) {
|
||||
return os_preloading;
|
||||
}
|
||||
|
||||
// Returns true if mimalloc was redirected
|
||||
mi_decl_nodiscard bool mi_is_redirected(void) mi_attr_noexcept {
|
||||
return mi_redirected;
|
||||
return _mi_is_redirected();
|
||||
}
|
||||
|
||||
// Communicate with the redirection module on Windows
|
||||
#if defined(_WIN32) && defined(MI_SHARED_LIB) && !defined(MI_WIN_NOREDIRECT)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
mi_decl_export void _mi_redirect_entry(DWORD reason) {
|
||||
// called on redirection; careful as this may be called before DllMain
|
||||
if (reason == DLL_PROCESS_ATTACH) {
|
||||
mi_redirected = true;
|
||||
}
|
||||
else if (reason == DLL_PROCESS_DETACH) {
|
||||
mi_redirected = false;
|
||||
}
|
||||
else if (reason == DLL_THREAD_DETACH) {
|
||||
mi_thread_done();
|
||||
}
|
||||
}
|
||||
__declspec(dllimport) bool mi_cdecl mi_allocator_init(const char** message);
|
||||
__declspec(dllimport) void mi_cdecl mi_allocator_done(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
static bool mi_allocator_init(const char** message) {
|
||||
if (message != NULL) *message = NULL;
|
||||
return true;
|
||||
}
|
||||
static void mi_allocator_done(void) {
|
||||
// nothing to do
|
||||
}
|
||||
#endif
|
||||
|
||||
// Called once by the process loader
|
||||
static void mi_process_load(void) {
|
||||
// Called once by the process loader from `src/prim/prim.c`
|
||||
void _mi_auto_process_init(void) {
|
||||
mi_heap_main_init();
|
||||
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
|
||||
volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true;
|
||||
@@ -504,17 +572,14 @@ static void mi_process_load(void) {
|
||||
#endif
|
||||
os_preloading = false;
|
||||
mi_assert_internal(_mi_is_main_thread());
|
||||
#if !(defined(_WIN32) && defined(MI_SHARED_LIB)) // use Dll process detach (see below) instead of atexit (issue #521)
|
||||
atexit(&mi_process_done);
|
||||
#endif
|
||||
_mi_options_init();
|
||||
mi_process_setup_auto_thread_done();
|
||||
mi_process_init();
|
||||
if (mi_redirected) _mi_verbose_message("malloc is redirected.\n");
|
||||
if (_mi_is_redirected()) _mi_verbose_message("malloc is redirected.\n");
|
||||
|
||||
// show message from the redirector (if present)
|
||||
const char* msg = NULL;
|
||||
mi_allocator_init(&msg);
|
||||
_mi_allocator_init(&msg);
|
||||
if (msg != NULL && (mi_option_is_enabled(mi_option_verbose) || mi_option_is_enabled(mi_option_show_errors))) {
|
||||
_mi_fputs(NULL,NULL,NULL,msg);
|
||||
}
|
||||
@@ -526,12 +591,15 @@ static void mi_process_load(void) {
|
||||
#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#include <intrin.h>
|
||||
mi_decl_cache_align bool _mi_cpu_has_fsrm = false;
|
||||
mi_decl_cache_align bool _mi_cpu_has_erms = false;
|
||||
|
||||
static void mi_detect_cpu_features(void) {
|
||||
// FSRM for fast rep movsb support (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017))
|
||||
// FSRM for fast short rep movsb/stosb support (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017))
|
||||
// EMRS for fast enhanced rep movsb/stosb support
|
||||
int32_t cpu_info[4];
|
||||
__cpuid(cpu_info, 7);
|
||||
_mi_cpu_has_fsrm = ((cpu_info[3] & (1 << 4)) != 0); // bit 4 of EDX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features>
|
||||
_mi_cpu_has_erms = ((cpu_info[1] & (1 << 9)) != 0); // bit 9 of EBX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features>
|
||||
}
|
||||
#else
|
||||
static void mi_detect_cpu_features(void) {
|
||||
@@ -540,13 +608,7 @@ static void mi_detect_cpu_features(void) {
|
||||
#endif
|
||||
|
||||
// Initialize the process; called by thread_init or the process loader
|
||||
void mi_process_init(void) mi_attr_noexcept {
|
||||
// ensure we are called once
|
||||
static mi_atomic_once_t process_init;
|
||||
#if _MSC_VER < 1920
|
||||
mi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main
|
||||
#endif
|
||||
if (!mi_atomic_once(&process_init)) return;
|
||||
static void mi_process_init_once(void) mi_attr_noexcept {
|
||||
_mi_process_is_initialized = true;
|
||||
_mi_verbose_message("process init: 0x%zx\n", _mi_thread_id());
|
||||
mi_process_setup_auto_thread_done();
|
||||
@@ -554,14 +616,6 @@ void mi_process_init(void) mi_attr_noexcept {
|
||||
mi_detect_cpu_features();
|
||||
_mi_os_init();
|
||||
mi_heap_main_init();
|
||||
#if MI_DEBUG
|
||||
_mi_verbose_message("debug level : %d\n", MI_DEBUG);
|
||||
#endif
|
||||
_mi_verbose_message("secure level: %d\n", MI_SECURE);
|
||||
_mi_verbose_message("mem tracking: %s\n", MI_TRACK_TOOL);
|
||||
#if MI_TSAN
|
||||
_mi_verbose_message("thread sanitizer enabled\n");
|
||||
#endif
|
||||
mi_thread_init();
|
||||
|
||||
#if defined(_WIN32)
|
||||
@@ -576,7 +630,7 @@ void mi_process_init(void) mi_attr_noexcept {
|
||||
|
||||
if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) {
|
||||
size_t pages = mi_option_get_clamp(mi_option_reserve_huge_os_pages, 0, 128*1024);
|
||||
long reserve_at = mi_option_get(mi_option_reserve_huge_os_pages_at);
|
||||
int reserve_at = (int)mi_option_get_clamp(mi_option_reserve_huge_os_pages_at, -1, INT_MAX);
|
||||
if (reserve_at != -1) {
|
||||
mi_reserve_huge_os_pages_at(pages, reserve_at, pages*500);
|
||||
} else {
|
||||
@@ -591,8 +645,19 @@ void mi_process_init(void) mi_attr_noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the process is done (through `at_exit`)
|
||||
static void mi_cdecl mi_process_done(void) {
|
||||
// Initialize the process; called by thread_init or the process loader
|
||||
void mi_process_init(void) mi_attr_noexcept {
|
||||
#if _MSC_VER < 1920
|
||||
mi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main
|
||||
#endif
|
||||
mi_atomic_do_once {
|
||||
mi_process_init_once();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called when the process is done
|
||||
static void mi_process_done_once(void) {
|
||||
// only shutdown if we were initialized
|
||||
if (!_mi_process_is_initialized) return;
|
||||
// ensure we are called once
|
||||
@@ -600,88 +665,53 @@ static void mi_cdecl mi_process_done(void) {
|
||||
if (process_done) return;
|
||||
process_done = true;
|
||||
|
||||
// get the default heap so we don't need to acces thread locals anymore
|
||||
mi_heap_t* heap = mi_prim_get_default_heap(); // use prim to not initialize any heap
|
||||
mi_assert_internal(heap != NULL);
|
||||
|
||||
// release any thread specific resources and ensure _mi_thread_done is called on all but the main thread
|
||||
_mi_prim_thread_done_auto_done();
|
||||
|
||||
|
||||
#ifndef MI_SKIP_COLLECT_ON_EXIT
|
||||
#if (MI_DEBUG || !defined(MI_SHARED_LIB))
|
||||
// free all memory if possible on process exit. This is not needed for a stand-alone process
|
||||
// but should be done if mimalloc is statically linked into another shared library which
|
||||
// is repeatedly loaded/unloaded, see issue #281.
|
||||
mi_collect(true /* force */ );
|
||||
mi_heap_collect(heap, true /* force */ );
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// done with tracking tools
|
||||
mi_track_done();
|
||||
|
||||
// Forcefully release all retained memory; this can be dangerous in general if overriding regular malloc/free
|
||||
// since after process_done there might still be other code running that calls `free` (like at_exit routines,
|
||||
// or C-runtime termination code.
|
||||
if (mi_option_is_enabled(mi_option_destroy_on_exit)) {
|
||||
mi_collect(true /* force */);
|
||||
_mi_heap_unsafe_destroy_all(); // forcefully release all memory held by all heaps (of this thread only!)
|
||||
_mi_arena_unsafe_destroy_all(& _mi_heap_main_get()->tld->stats);
|
||||
mi_heap_collect(heap, true /* force */);
|
||||
_mi_heap_unsafe_destroy_all(heap); // forcefully release all memory held by all heaps (of this thread only!)
|
||||
_mi_arena_unsafe_destroy_all();
|
||||
_mi_segment_map_unsafe_destroy();
|
||||
}
|
||||
|
||||
if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) {
|
||||
mi_stats_print(NULL);
|
||||
}
|
||||
mi_allocator_done();
|
||||
_mi_allocator_done();
|
||||
_mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id);
|
||||
os_preloading = true; // don't call the C runtime anymore
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(_WIN32) && defined(MI_SHARED_LIB)
|
||||
// Windows DLL: easy to hook into process_init and thread_done
|
||||
__declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
|
||||
MI_UNUSED(reserved);
|
||||
MI_UNUSED(inst);
|
||||
if (reason==DLL_PROCESS_ATTACH) {
|
||||
mi_process_load();
|
||||
}
|
||||
else if (reason==DLL_PROCESS_DETACH) {
|
||||
mi_process_done();
|
||||
}
|
||||
else if (reason==DLL_THREAD_DETACH) {
|
||||
if (!mi_is_redirected()) {
|
||||
mi_thread_done();
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
// Called when the process is done (cdecl as it is used with `at_exit` on some platforms)
|
||||
void mi_cdecl mi_process_done(void) mi_attr_noexcept {
|
||||
mi_atomic_do_once {
|
||||
mi_process_done_once();
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
// MSVC: use data section magic for static libraries
|
||||
// See <https://www.codeguru.com/cpp/misc/misc/applicationcontrol/article.php/c6945/Running-Code-Before-and-After-Main.htm>
|
||||
static int _mi_process_init(void) {
|
||||
mi_process_load();
|
||||
return 0;
|
||||
}
|
||||
typedef int(*_mi_crt_callback_t)(void);
|
||||
#if defined(_M_X64) || defined(_M_ARM64)
|
||||
__pragma(comment(linker, "/include:" "_mi_msvc_initu"))
|
||||
#pragma section(".CRT$XIU", long, read)
|
||||
#else
|
||||
__pragma(comment(linker, "/include:" "__mi_msvc_initu"))
|
||||
#endif
|
||||
#pragma data_seg(".CRT$XIU")
|
||||
mi_decl_externc _mi_crt_callback_t _mi_msvc_initu[] = { &_mi_process_init };
|
||||
#pragma data_seg()
|
||||
|
||||
#elif defined(__cplusplus)
|
||||
// C++: use static initialization to detect process start
|
||||
static bool _mi_process_init(void) {
|
||||
mi_process_load();
|
||||
return (_mi_heap_main.thread_id != 0);
|
||||
}
|
||||
static bool mi_initialized = _mi_process_init();
|
||||
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
// GCC,Clang: use the constructor attribute
|
||||
static void __attribute__((constructor)) _mi_process_init(void) {
|
||||
mi_process_load();
|
||||
}
|
||||
|
||||
#else
|
||||
#pragma message("define a way to call mi_process_load on your platform")
|
||||
#endif
|
||||
void mi_cdecl _mi_auto_process_done(void) mi_attr_noexcept {
|
||||
if (_mi_option_get_fast(mi_option_destroy_on_exit)>1) return;
|
||||
mi_process_done();
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-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.
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
// --------------------------------------------------------
|
||||
// This module defines various std libc functions to reduce
|
||||
// the dependency on libc, and also prevent errors caused
|
||||
// by some libc implementations when called before `main`
|
||||
// executes (due to malloc redirection)
|
||||
// --------------------------------------------------------
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/prim.h" // mi_prim_getenv
|
||||
|
||||
char _mi_toupper(char c) {
|
||||
if (c >= 'a' && c <= 'z') return (c - 'a' + 'A');
|
||||
else return c;
|
||||
}
|
||||
|
||||
int _mi_strnicmp(const char* s, const char* t, size_t n) {
|
||||
mi_assert_internal(s!=NULL && t!=NULL);
|
||||
if (n == 0) return 0;
|
||||
for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) {
|
||||
if (_mi_toupper(*s) != _mi_toupper(*t)) break;
|
||||
}
|
||||
return (n == 0 ? 0 : *s - *t);
|
||||
}
|
||||
|
||||
bool _mi_streq(const char* s, const char* t) {
|
||||
if (s==NULL && t==NULL) return true;
|
||||
if (s==NULL || t==NULL) return false;
|
||||
for (; *s != 0 && *t != 0; s++, t++) {
|
||||
if (*s != *t) break;
|
||||
}
|
||||
return (*s == *t);
|
||||
}
|
||||
|
||||
bool _mi_strlcpy(char* dest, const char* src, size_t dest_size) {
|
||||
if (dest==NULL || src==NULL || dest_size == 0) return (src==NULL || *src==0);
|
||||
// copy until end of src, or when dest is (almost) full
|
||||
while (*src != 0 && dest_size > 1) {
|
||||
*dest++ = *src++;
|
||||
dest_size--;
|
||||
}
|
||||
// always zero terminate
|
||||
*dest = 0;
|
||||
return (*src == 0);
|
||||
}
|
||||
|
||||
bool _mi_strlcat(char* dest, const char* src, size_t dest_size) {
|
||||
if (dest==NULL || src==NULL || dest_size == 0) return (src==NULL || *src==0);
|
||||
// find end of string in the dest buffer
|
||||
while (*dest != 0 && dest_size > 1) {
|
||||
dest++;
|
||||
dest_size--;
|
||||
}
|
||||
// and catenate
|
||||
return _mi_strlcpy(dest, src, dest_size);
|
||||
}
|
||||
|
||||
size_t _mi_strnlen(const char* s, size_t max_len) {
|
||||
if (s==NULL) return 0;
|
||||
size_t len = 0;
|
||||
while(len < max_len && s[len] != 0) { len++; }
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t _mi_strlen(const char* s) {
|
||||
return _mi_strnlen(s,PTRDIFF_MAX);
|
||||
}
|
||||
|
||||
#ifdef MI_NO_GETENV
|
||||
int _mi_getenv(const char* name, char* result, size_t result_size) {
|
||||
MI_UNUSED(name);
|
||||
MI_UNUSED(result);
|
||||
MI_UNUSED(result_size);
|
||||
return ENOENT;
|
||||
}
|
||||
#else
|
||||
int _mi_getenv(const char* name, char* result, size_t result_size) {
|
||||
if (name==NULL || result == NULL || result_size < 64) return ENOENT;
|
||||
// change the result of _mi_prim_getenv to an errno result
|
||||
const int res = _mi_prim_getenv(name,result,result_size);
|
||||
return (res > 0 ? 0 : (res == 0 ? ENOENT : EAGAIN));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Define our own primitives for doing an action once
|
||||
// --------------------------------------------------------
|
||||
|
||||
// Returns `true` only on the first invocation, signifying we can execute an action once.
|
||||
// If it returns `true`, the caller should call `_mi_atomic_once_release` after performing the action.
|
||||
// Other threads (than the initial thread that entered) will block until `_mi_atomic_once_release` has been called.
|
||||
bool _mi_atomic_once_enter(mi_atomic_once_t* once) {
|
||||
const uintptr_t once_tid = mi_atomic_load_acquire(&once->tid);
|
||||
if mi_likely(once_tid == 1) {
|
||||
return false; // already executed
|
||||
}
|
||||
const mi_threadid_t current_tid = _mi_thread_id();
|
||||
if (once_tid == current_tid) {
|
||||
return false; // recursive invocation; we need this for process_init for example
|
||||
}
|
||||
|
||||
mi_lock_acquire(&once->lock);
|
||||
uintptr_t expected = 0;
|
||||
if (mi_atomic_cas_strong_acq_rel(&once->tid, &expected, current_tid)) { // could use atomic_load/store as well
|
||||
return true; // should execute and release
|
||||
}
|
||||
else {
|
||||
mi_lock_release(&once->lock);
|
||||
return false; // already another thread entered and released
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_atomic_once_release(mi_atomic_once_t* once) {
|
||||
if (mi_atomic_load_acquire(&once->tid)>1) { // paranoia
|
||||
mi_atomic_store_release(&once->tid,1); // done executing
|
||||
mi_lock_release(&once->lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Define our own limited `_mi_vsnprintf` and `_mi_snprintf`
|
||||
// This is mostly to avoid calling these when libc is not yet
|
||||
// initialized (and to reduce dependencies)
|
||||
//
|
||||
// format: d i, p x u, s
|
||||
// prec: z l ll L
|
||||
// width: 10
|
||||
// align-left: -
|
||||
// fill: 0
|
||||
// plus: +
|
||||
// --------------------------------------------------------
|
||||
|
||||
static void mi_outc(char c, char** out, char* end) {
|
||||
char* p = *out;
|
||||
if (p >= end) return;
|
||||
*p = c;
|
||||
*out = p + 1;
|
||||
}
|
||||
|
||||
static void mi_outs(const char* s, char** out, char* end) {
|
||||
if (s == NULL) return;
|
||||
char* p = *out;
|
||||
while (*s != 0 && p < end) {
|
||||
*p++ = *s++;
|
||||
}
|
||||
*out = p;
|
||||
}
|
||||
|
||||
static void mi_out_fill(char fill, size_t len, char** out, char* end) {
|
||||
char* p = *out;
|
||||
for (size_t i = 0; i < len && p < end; i++) {
|
||||
*p++ = fill;
|
||||
}
|
||||
*out = p;
|
||||
}
|
||||
|
||||
static void mi_out_alignright(char fill, char* start, size_t len, size_t extra, char* end) {
|
||||
if (len == 0 || extra == 0) return;
|
||||
if (start + len + extra >= end) return;
|
||||
// move `len` characters to the right (in reverse since it can overlap)
|
||||
for (size_t i = 1; i <= len; i++) {
|
||||
start[len + extra - i] = start[len - i];
|
||||
}
|
||||
// and fill the start
|
||||
for (size_t i = 0; i < extra; i++) {
|
||||
start[i] = fill;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void mi_out_num(uintmax_t x, size_t base, char prefix, char** out, char* end)
|
||||
{
|
||||
if (x == 0 || base == 0 || base > 16) {
|
||||
if (prefix != 0) { mi_outc(prefix, out, end); }
|
||||
mi_outc('0',out,end);
|
||||
}
|
||||
else {
|
||||
#define MI_MAX_OUT_DIGITS (160) /* a 512 bit number has 155 digits */
|
||||
char num[MI_MAX_OUT_DIGITS];
|
||||
int dcount = 0;
|
||||
while(x>0 && dcount < MI_MAX_OUT_DIGITS) {
|
||||
char digit = (char)(x % base);
|
||||
num[dcount++] = (digit <= 9 ? '0' + digit : 'A' + digit - 10);
|
||||
x = x / base;
|
||||
}
|
||||
if (dcount>=MI_MAX_OUT_DIGITS) return; // don't output anything?
|
||||
if (prefix != 0) {
|
||||
mi_outc(prefix, out, end);
|
||||
}
|
||||
while(dcount-- > 0) {
|
||||
mi_outc(num[dcount], out, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define MI_NEXTC() c = *in; if (c==0) break; in++;
|
||||
|
||||
int _mi_vsnprintf(char* buf, size_t bufsize, const char* fmt, va_list args) {
|
||||
if (buf == NULL || bufsize == 0 || fmt == NULL) return 0;
|
||||
buf[bufsize - 1] = 0;
|
||||
char* const end = buf + (bufsize - 1);
|
||||
const char* in = fmt;
|
||||
char* out = buf;
|
||||
while (true) {
|
||||
if (out >= end) break;
|
||||
char c;
|
||||
MI_NEXTC();
|
||||
if (c != '%') {
|
||||
if ((c >= ' ' && c <= '~') || c=='\n' || c=='\r' || c=='\t') { // output visible ascii or standard control only
|
||||
mi_outc(c, &out, end);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MI_NEXTC();
|
||||
char fill = ' ';
|
||||
size_t width = 0;
|
||||
char numtype = 'd';
|
||||
char numplus = 0;
|
||||
bool alignright = true;
|
||||
if (c == '+' || c == ' ') { numplus = c; MI_NEXTC(); }
|
||||
if (c == '-') { alignright = false; MI_NEXTC(); }
|
||||
if (c == '0') { fill = '0'; MI_NEXTC(); }
|
||||
if (c >= '1' && c <= '9') {
|
||||
width = (c - '0'); MI_NEXTC();
|
||||
while (c >= '0' && c <= '9') {
|
||||
if (width < SIZE_MAX/1024) { // no overflow
|
||||
width = (10 * width) + (c - '0');
|
||||
}
|
||||
MI_NEXTC();
|
||||
}
|
||||
if (c == 0) break; // extra check due to while
|
||||
}
|
||||
if (c == 'z' || c == 't' || c == 'L') { numtype = c; MI_NEXTC(); }
|
||||
else if (c == 'l') {
|
||||
numtype = c; MI_NEXTC();
|
||||
if (c == 'l') { numtype = 'L'; MI_NEXTC(); }
|
||||
}
|
||||
|
||||
char* start = out;
|
||||
if (c == 's') {
|
||||
// string
|
||||
const char* s = va_arg(args, const char*);
|
||||
mi_outs(s, &out, end);
|
||||
}
|
||||
else if (c == 'p' || c == 'x' || c == 'u') {
|
||||
// unsigned
|
||||
uintmax_t x = 0;
|
||||
if (c == 'x' || c == 'u') {
|
||||
if (numtype == 'z') x = va_arg(args, size_t);
|
||||
else if (numtype == 't') x = va_arg(args, uintptr_t); // unsigned ptrdiff_t
|
||||
else if (numtype == 'L') x = va_arg(args, unsigned long long);
|
||||
else if (numtype == 'l') x = va_arg(args, unsigned long);
|
||||
else x = va_arg(args, unsigned int);
|
||||
}
|
||||
else if (c == 'p') {
|
||||
void* const p = va_arg(args, void*);
|
||||
x = (uintptr_t)p;
|
||||
mi_outs("0x", &out, end);
|
||||
start = out;
|
||||
width = (width >= 2 ? width - 2 : 0);
|
||||
}
|
||||
if (width == 0 && (c == 'x' || c == 'p')) {
|
||||
if (c == 'p') { width = 2 * (x <= UINT32_MAX ? 4 : ((x >> 16) <= UINT32_MAX ? 6 : sizeof(void*))); }
|
||||
if (width == 0) { width = 2; }
|
||||
if (alignright) { fill = '0'; }
|
||||
}
|
||||
mi_out_num(x, (c == 'x' || c == 'p' ? 16 : 10), numplus, &out, end);
|
||||
}
|
||||
else if (c == 'i' || c == 'd') {
|
||||
// signed
|
||||
intmax_t x = 0;
|
||||
if (numtype == 'z') x = va_arg(args, intptr_t );
|
||||
else if (numtype == 't') x = va_arg(args, ptrdiff_t);
|
||||
else if (numtype == 'L') x = va_arg(args, long long);
|
||||
else if (numtype == 'l') x = va_arg(args, long);
|
||||
else x = va_arg(args, int);
|
||||
char pre = 0;
|
||||
if (x < 0) {
|
||||
pre = '-';
|
||||
if (x > INTMAX_MIN) { x = -x; }
|
||||
}
|
||||
else if (numplus != 0) {
|
||||
pre = numplus;
|
||||
}
|
||||
mi_out_num((uintmax_t)x, 10, pre, &out, end);
|
||||
}
|
||||
else if (c >= ' ' && c <= '~') {
|
||||
// unknown format
|
||||
mi_outc('%', &out, end);
|
||||
mi_outc(c, &out, end);
|
||||
}
|
||||
|
||||
// fill & align
|
||||
mi_assert_internal(out <= end);
|
||||
mi_assert_internal(out >= start);
|
||||
const size_t len = out - start;
|
||||
if (len < width) {
|
||||
mi_out_fill(fill, width - len, &out, end);
|
||||
if (alignright && out <= end) {
|
||||
mi_out_alignright(fill, start, len, width - len, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mi_assert_internal(out <= end);
|
||||
*out = 0;
|
||||
return (int)(out - buf);
|
||||
}
|
||||
|
||||
int _mi_snprintf(char* buf, size_t buflen, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
const int written = _mi_vsnprintf(buf, buflen, fmt, args);
|
||||
va_end(args);
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
#if MI_SIZE_SIZE == 4
|
||||
#define mi_mask_even_bits32 (0x55555555)
|
||||
#define mi_mask_even_pairs32 (0x33333333)
|
||||
#define mi_mask_even_nibbles32 (0x0F0F0F0F)
|
||||
|
||||
// sum of all the bytes in `x` if it is guaranteed that the sum < 256!
|
||||
static size_t mi_byte_sum32(uint32_t x) {
|
||||
// perform `x * 0x01010101`: the highest byte contains the sum of all bytes.
|
||||
x += (x << 8);
|
||||
x += (x << 16);
|
||||
return (size_t)(x >> 24);
|
||||
}
|
||||
|
||||
static size_t mi_popcount_generic32(uint32_t x) {
|
||||
// first count each 2-bit group `a`, where: a==0b00 -> 00, a==0b01 -> 01, a==0b10 -> 01, a==0b11 -> 10
|
||||
// in other words, `a - (a>>1)`; to do this in parallel, we need to mask to prevent spilling a bit pair
|
||||
// into the lower bit-pair:
|
||||
x = x - ((x >> 1) & mi_mask_even_bits32);
|
||||
// add the 2-bit pair results
|
||||
x = (x & mi_mask_even_pairs32) + ((x >> 2) & mi_mask_even_pairs32);
|
||||
// add the 4-bit nibble results
|
||||
x = (x + (x >> 4)) & mi_mask_even_nibbles32;
|
||||
// each byte now has a count of its bits, we can sum them now:
|
||||
return mi_byte_sum32(x);
|
||||
}
|
||||
|
||||
mi_decl_noinline size_t _mi_popcount_generic(size_t x) {
|
||||
return mi_popcount_generic32(x);
|
||||
}
|
||||
|
||||
#else
|
||||
#define mi_mask_even_bits64 (0x5555555555555555)
|
||||
#define mi_mask_even_pairs64 (0x3333333333333333)
|
||||
#define mi_mask_even_nibbles64 (0x0F0F0F0F0F0F0F0F)
|
||||
|
||||
// sum of all the bytes in `x` if it is guaranteed that the sum < 256!
|
||||
static size_t mi_byte_sum64(uint64_t x) {
|
||||
x += (x << 8);
|
||||
x += (x << 16);
|
||||
x += (x << 32);
|
||||
return (size_t)(x >> 56);
|
||||
}
|
||||
|
||||
static size_t mi_popcount_generic64(uint64_t x) {
|
||||
x = x - ((x >> 1) & mi_mask_even_bits64);
|
||||
x = (x & mi_mask_even_pairs64) + ((x >> 2) & mi_mask_even_pairs64);
|
||||
x = (x + (x >> 4)) & mi_mask_even_nibbles64;
|
||||
return mi_byte_sum64(x);
|
||||
}
|
||||
|
||||
mi_decl_noinline size_t _mi_popcount_generic(size_t x) {
|
||||
return mi_popcount_generic64(x);
|
||||
}
|
||||
#endif
|
||||
|
||||
+270
-157
@@ -9,9 +9,9 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h" // mi_prim_out_stderr
|
||||
|
||||
#include <stdio.h> // FILE
|
||||
#include <stdio.h> // stdin/stdout
|
||||
#include <stdlib.h> // abort
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
|
||||
static long mi_max_error_count = 16; // stop outputting errors after this (use < 0 for no limit)
|
||||
@@ -47,6 +47,67 @@ typedef struct mi_option_desc_s {
|
||||
#define MI_OPTION(opt) mi_option_##opt, #opt, NULL
|
||||
#define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy
|
||||
|
||||
// Some options can be set at build time for statically linked libraries
|
||||
// (use `-DMI_EXTRA_CPPDEFS="opt1=val1;opt2=val2"`)
|
||||
//
|
||||
// This is useful if we cannot pass them as environment variables
|
||||
// (and setting them programmatically would be too late)
|
||||
|
||||
#ifndef MI_DEFAULT_VERBOSE
|
||||
#define MI_DEFAULT_VERBOSE 0
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_EAGER_COMMIT
|
||||
#define MI_DEFAULT_EAGER_COMMIT 1
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_ARENA_EAGER_COMMIT
|
||||
#define MI_DEFAULT_ARENA_EAGER_COMMIT 2
|
||||
#endif
|
||||
|
||||
// in KiB
|
||||
#ifndef MI_DEFAULT_ARENA_RESERVE
|
||||
#if (MI_INTPTR_SIZE>4)
|
||||
#define MI_DEFAULT_ARENA_RESERVE 1024L*1024L
|
||||
#else
|
||||
#define MI_DEFAULT_ARENA_RESERVE 128L*1024L
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_DISALLOW_ARENA_ALLOC
|
||||
#define MI_DEFAULT_DISALLOW_ARENA_ALLOC 0
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_ALLOW_LARGE_OS_PAGES
|
||||
#define MI_DEFAULT_ALLOW_LARGE_OS_PAGES 0
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_RESERVE_HUGE_OS_PAGES
|
||||
#define MI_DEFAULT_RESERVE_HUGE_OS_PAGES 0
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_RESERVE_OS_MEMORY
|
||||
#define MI_DEFAULT_RESERVE_OS_MEMORY 0
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_GUARDED_SAMPLE_RATE
|
||||
#if MI_GUARDED && !MI_DEBUG
|
||||
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 4000
|
||||
#else
|
||||
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MI_DEFAULT_ALLOW_THP
|
||||
#if defined(__ANDROID__)
|
||||
#define MI_DEFAULT_ALLOW_THP 0
|
||||
#else
|
||||
#define MI_DEFAULT_ALLOW_THP 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Static options
|
||||
static mi_option_desc_t options[_mi_option_last] =
|
||||
{
|
||||
// stable options
|
||||
@@ -56,16 +117,21 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||
{ 0, UNINIT, MI_OPTION(show_errors) },
|
||||
#endif
|
||||
{ 0, UNINIT, MI_OPTION(show_stats) },
|
||||
{ 0, UNINIT, MI_OPTION(verbose) },
|
||||
{ MI_DEFAULT_VERBOSE, UNINIT, MI_OPTION(verbose) },
|
||||
|
||||
// the following options are experimental and not all combinations make sense.
|
||||
{ 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (4MiB) (but see also `eager_commit_delay`)
|
||||
{ 2, UNINIT, MI_OPTION_LEGACY(arena_eager_commit,eager_region_commit) }, // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux)
|
||||
// some of the following options are experimental and not all combinations are allowed.
|
||||
{ MI_DEFAULT_EAGER_COMMIT,
|
||||
UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (4MiB) (but see also `eager_commit_delay`)
|
||||
{ MI_DEFAULT_ARENA_EAGER_COMMIT,
|
||||
UNINIT, MI_OPTION_LEGACY(arena_eager_commit,eager_region_commit) }, // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux)
|
||||
{ 1, UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) }, // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)
|
||||
{ 0, UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
|
||||
{ 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
|
||||
{ MI_DEFAULT_ALLOW_LARGE_OS_PAGES,
|
||||
UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
|
||||
{ MI_DEFAULT_RESERVE_HUGE_OS_PAGES,
|
||||
UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
|
||||
{-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N
|
||||
{ 0, UNINIT, MI_OPTION(reserve_os_memory) },
|
||||
{ MI_DEFAULT_RESERVE_OS_MEMORY,
|
||||
UNINIT, MI_OPTION(reserve_os_memory) }, // reserve N KiB OS memory in advance (use `option_get_size`)
|
||||
{ 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
|
||||
{ 0, UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free
|
||||
{ 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_purge,abandoned_page_reset) }, // reset free page memory when a thread terminates
|
||||
@@ -77,40 +143,113 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||
#endif
|
||||
{ 10, UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds
|
||||
{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
|
||||
{ 0, UNINIT, MI_OPTION(limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
|
||||
{ 0, UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
|
||||
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
|
||||
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
||||
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
||||
{ 8, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. number of segment reclaims from the abandoned segments per try.
|
||||
{ 32, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
||||
{ 32, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
||||
{ 10, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. percentage of the abandoned segments to be reclaimed per try.
|
||||
{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
|
||||
#if (MI_INTPTR_SIZE>4)
|
||||
{ 1024L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time
|
||||
#else
|
||||
{ 128L * 1024L, UNINIT, MI_OPTION(arena_reserve) },
|
||||
#endif
|
||||
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
||||
{ MI_DEFAULT_ARENA_RESERVE, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)
|
||||
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
||||
{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
|
||||
{ 0, UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free
|
||||
{ MI_DEFAULT_DISALLOW_ARENA_ALLOC, UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
||||
{ 400, UNINIT, MI_OPTION(retry_on_oom) }, // windows only: retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries.
|
||||
#if defined(MI_VISIT_ABANDONED)
|
||||
{ 1, INITIALIZED, MI_OPTION(visit_abandoned) }, // allow visiting heap blocks in abandoned segments; requires taking locks during reclaim.
|
||||
#else
|
||||
{ 0, UNINIT, MI_OPTION(visit_abandoned) },
|
||||
#endif
|
||||
{ 0, UNINIT, MI_OPTION(guarded_min) }, // only used when building with MI_GUARDED: minimal rounded object size for guarded objects
|
||||
{ MI_GiB, UNINIT, MI_OPTION(guarded_max) }, // only used when building with MI_GUARDED: maximal rounded object size for guarded objects
|
||||
{ 0, UNINIT, MI_OPTION(guarded_precise) }, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
|
||||
{ MI_DEFAULT_GUARDED_SAMPLE_RATE,
|
||||
UNINIT, MI_OPTION(guarded_sample_rate)}, // 1 out of N allocations in the min/max range will be guarded (=4000)
|
||||
{ 0, UNINIT, MI_OPTION(guarded_sample_seed)},
|
||||
{ 0, UNINIT, MI_OPTION(target_segments_per_thread) }, // abandon segments beyond this point, or 0 to disable.
|
||||
{ 10000, UNINIT, MI_OPTION(generic_collect) }, // collect heaps every N (=10000) generic allocation calls
|
||||
{ MI_DEFAULT_ALLOW_THP,
|
||||
UNINIT, MI_OPTION(allow_thp) } // allow transparent huge pages?
|
||||
};
|
||||
|
||||
static void mi_option_init(mi_option_desc_t* desc);
|
||||
|
||||
static bool mi_option_has_size_in_kib(mi_option_t option) {
|
||||
return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);
|
||||
}
|
||||
|
||||
void _mi_options_init(void) {
|
||||
// called on process load; should not be called before the CRT is initialized!
|
||||
// (e.g. do not call this from process_init as that may run before CRT initialization)
|
||||
// called on process load
|
||||
mi_add_stderr_output(); // now it safe to use stderr for output
|
||||
for(int i = 0; i < _mi_option_last; i++ ) {
|
||||
mi_option_t option = (mi_option_t)i;
|
||||
long l = mi_option_get(option); MI_UNUSED(l); // initialize
|
||||
// if (option != mi_option_verbose)
|
||||
{
|
||||
mi_option_desc_t* desc = &options[option];
|
||||
_mi_verbose_message("option '%s': %ld\n", desc->name, desc->value);
|
||||
}
|
||||
}
|
||||
mi_max_error_count = mi_option_get(mi_option_max_errors);
|
||||
mi_max_warning_count = mi_option_get(mi_option_max_warnings);
|
||||
#if MI_GUARDED
|
||||
if (mi_option_get(mi_option_guarded_sample_rate) > 0) {
|
||||
if (mi_option_is_enabled(mi_option_allow_large_os_pages)) {
|
||||
mi_option_disable(mi_option_allow_large_os_pages);
|
||||
_mi_warning_message("option 'allow_large_os_pages' is disabled to allow for guarded objects\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (mi_option_is_enabled(mi_option_verbose)) { mi_options_print(); }
|
||||
}
|
||||
|
||||
#define mi_stringifyx(str) #str // and stringify
|
||||
#define mi_stringify(str) mi_stringifyx(str) // expand
|
||||
|
||||
void mi_options_print(void) mi_attr_noexcept
|
||||
{
|
||||
// show version
|
||||
const int vermajor = MI_MALLOC_VERSION/10000;
|
||||
const int verminor = (MI_MALLOC_VERSION%10000)/100;
|
||||
const int verpatch = (MI_MALLOC_VERSION%100);
|
||||
_mi_message("v%i.%i.%i%s%s\n", vermajor, verminor, verpatch,
|
||||
#if defined(MI_CMAKE_BUILD_TYPE)
|
||||
", " mi_stringify(MI_CMAKE_BUILD_TYPE)
|
||||
#else
|
||||
""
|
||||
#endif
|
||||
,
|
||||
#if defined(MI_GIT_DESCRIBE)
|
||||
", git " mi_stringify(MI_GIT_DESCRIBE)
|
||||
#else
|
||||
""
|
||||
#endif
|
||||
);
|
||||
|
||||
// show options
|
||||
for (int i = 0; i < _mi_option_last; i++) {
|
||||
mi_option_t option = (mi_option_t)i;
|
||||
long l = mi_option_get(option); MI_UNUSED(l); // possibly initialize
|
||||
mi_option_desc_t* desc = &options[option];
|
||||
_mi_message("option '%s': %ld %s\n", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? "KiB" : ""));
|
||||
}
|
||||
|
||||
// show build configuration
|
||||
_mi_message("debug level : %d\n", MI_DEBUG );
|
||||
_mi_message("secure level: %d\n", MI_SECURE );
|
||||
_mi_message("mem tracking: %s\n", MI_TRACK_TOOL);
|
||||
#if MI_GUARDED
|
||||
_mi_message("guarded build: %s\n", mi_option_get(mi_option_guarded_sample_rate) != 0 ? "enabled" : "disabled");
|
||||
#endif
|
||||
#if MI_TSAN
|
||||
_mi_message("thread santizer enabled\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
long _mi_option_get_fast(mi_option_t option) {
|
||||
mi_assert(option >= 0 && option < _mi_option_last);
|
||||
mi_option_desc_t* desc = &options[option];
|
||||
mi_assert(desc->option == option); // index should match the option
|
||||
//mi_assert(desc->init != UNINIT);
|
||||
return desc->value;
|
||||
}
|
||||
|
||||
|
||||
mi_decl_nodiscard long mi_option_get(mi_option_t option) {
|
||||
mi_assert(option >= 0 && option < _mi_option_last);
|
||||
if (option < 0 || option >= _mi_option_last) return 0;
|
||||
@@ -128,9 +267,14 @@ mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long ma
|
||||
}
|
||||
|
||||
mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {
|
||||
mi_assert_internal(option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);
|
||||
long x = mi_option_get(option);
|
||||
return (x < 0 ? 0 : (size_t)x * MI_KiB);
|
||||
const long x = mi_option_get(option);
|
||||
size_t size = (x < 0 ? 0 : (size_t)x);
|
||||
if (mi_option_has_size_in_kib(option)) {
|
||||
if (mi_mul_overflow(size, MI_KiB, &size)) {
|
||||
size = MI_MAX_ALLOC_SIZE;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void mi_option_set(mi_option_t option, long value) {
|
||||
@@ -140,6 +284,13 @@ void mi_option_set(mi_option_t option, long value) {
|
||||
mi_assert(desc->option == option); // index should match the option
|
||||
desc->value = value;
|
||||
desc->init = INITIALIZED;
|
||||
// ensure min/max range; be careful to not recurse.
|
||||
if (desc->option == mi_option_guarded_min && _mi_option_get_fast(mi_option_guarded_max) < value) {
|
||||
mi_option_set(mi_option_guarded_max, value);
|
||||
}
|
||||
else if (desc->option == mi_option_guarded_max && _mi_option_get_fast(mi_option_guarded_min) > value) {
|
||||
mi_option_set(mi_option_guarded_min, value);
|
||||
}
|
||||
}
|
||||
|
||||
void mi_option_set_default(mi_option_t option, long value) {
|
||||
@@ -183,37 +334,43 @@ static void mi_cdecl mi_out_stderr(const char* msg, void* arg) {
|
||||
// an output function is registered it is called immediately with
|
||||
// the output up to that point.
|
||||
#ifndef MI_MAX_DELAY_OUTPUT
|
||||
#define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024))
|
||||
#define MI_MAX_DELAY_OUTPUT ((size_t)(16*1024))
|
||||
#endif
|
||||
static char out_buf[MI_MAX_DELAY_OUTPUT+1];
|
||||
static _Atomic(size_t) out_len;
|
||||
static mi_lock_t out_buf_lock = MI_LOCK_INITIALIZER;
|
||||
|
||||
static void mi_cdecl mi_out_buf(const char* msg, void* arg) {
|
||||
MI_UNUSED(arg);
|
||||
if (msg==NULL) return;
|
||||
if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return;
|
||||
if (mi_atomic_load_acquire(&out_len)>=MI_MAX_DELAY_OUTPUT) return;
|
||||
size_t n = _mi_strlen(msg);
|
||||
if (n==0) return;
|
||||
// claim space
|
||||
size_t start = mi_atomic_add_acq_rel(&out_len, n);
|
||||
if (start >= MI_MAX_DELAY_OUTPUT) return;
|
||||
// check bound
|
||||
if (start+n >= MI_MAX_DELAY_OUTPUT) {
|
||||
n = MI_MAX_DELAY_OUTPUT-start-1;
|
||||
if (n==0 || n >= MI_MAX_DELAY_OUTPUT) return;
|
||||
// copy msg into the buffer
|
||||
mi_lock(&out_buf_lock) {
|
||||
const size_t start = mi_atomic_add_acq_rel(&out_len, n);
|
||||
if (start < MI_MAX_DELAY_OUTPUT) {
|
||||
// check bound
|
||||
if (start+n >= MI_MAX_DELAY_OUTPUT) {
|
||||
n = MI_MAX_DELAY_OUTPUT-start-1;
|
||||
}
|
||||
_mi_memcpy(&out_buf[start], msg, n);
|
||||
}
|
||||
}
|
||||
_mi_memcpy(&out_buf[start], msg, n);
|
||||
}
|
||||
|
||||
static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {
|
||||
if (out==NULL) return;
|
||||
// claim (if `no_more_buf == true`, no more output will be added after this point)
|
||||
size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));
|
||||
// and output the current contents
|
||||
if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;
|
||||
out_buf[count] = 0;
|
||||
out(out_buf,arg);
|
||||
if (!no_more_buf) {
|
||||
out_buf[count] = '\n'; // if continue with the buffer, insert a newline
|
||||
mi_lock(&out_buf_lock) {
|
||||
size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));
|
||||
// and output the current contents
|
||||
if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;
|
||||
out_buf[count] = 0;
|
||||
out(out_buf,arg);
|
||||
if (!no_more_buf) {
|
||||
out_buf[count] = '\n'; // if continue with the buffer, insert a newline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,28 +388,29 @@ static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {
|
||||
// Default output handler
|
||||
// --------------------------------------------------------
|
||||
|
||||
// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.
|
||||
// For now, don't register output from multiple threads.
|
||||
static mi_output_fun* volatile mi_out_default; // = NULL
|
||||
// The program should only install a single output handler from a single thread
|
||||
// since otherwise the argument and output function may not match.
|
||||
static _Atomic(void*) mi_out_default; // = // is `mi_output_fun*` (but some platforms don't support atomic function pointers)
|
||||
static _Atomic(void*) mi_out_arg; // = NULL
|
||||
|
||||
static mi_output_fun* mi_out_get_default(void** parg) {
|
||||
mi_output_fun* const out = (mi_output_fun*)mi_atomic_load_ptr_acquire(void,&mi_out_default);
|
||||
if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }
|
||||
mi_output_fun* out = mi_out_default;
|
||||
return (out == NULL ? &mi_out_buf : out);
|
||||
}
|
||||
|
||||
void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {
|
||||
mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer
|
||||
mi_atomic_store_ptr_release(void,&mi_out_default, (void*)(out == NULL ? &mi_out_stderr : out)); // stop using the delayed output buffer
|
||||
mi_atomic_store_ptr_release(void,&mi_out_arg, arg);
|
||||
if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now
|
||||
if (out!=NULL) { mi_out_buf_flush(out,true,arg); } // output all the delayed output now
|
||||
}
|
||||
|
||||
// add stderr to the delayed output after the module is loaded
|
||||
static void mi_add_stderr_output(void) {
|
||||
mi_assert_internal(mi_out_default == NULL);
|
||||
mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
|
||||
mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output
|
||||
mi_atomic_store_ptr_release(void,&mi_out_default,(void*)&mi_out_buf_stderr); // and add stderr to the delayed output
|
||||
mi_atomic_store_ptr_release(void,&mi_out_arg,NULL);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
@@ -283,14 +441,14 @@ static mi_decl_noinline void mi_recurse_exit_prim(void) {
|
||||
}
|
||||
|
||||
static bool mi_recurse_enter(void) {
|
||||
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
|
||||
#if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)
|
||||
if (_mi_preloading()) return false;
|
||||
#endif
|
||||
return mi_recurse_enter_prim();
|
||||
}
|
||||
|
||||
static void mi_recurse_exit(void) {
|
||||
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
|
||||
#if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)
|
||||
if (_mi_preloading()) return;
|
||||
#endif
|
||||
mi_recurse_exit_prim();
|
||||
@@ -311,12 +469,12 @@ void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* me
|
||||
}
|
||||
|
||||
// Define our own limited `fprintf` that avoids memory allocation.
|
||||
// We do this using `snprintf` with a limited buffer.
|
||||
// We do this using `_mi_vsnprintf` with a limited buffer.
|
||||
static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {
|
||||
char buf[512];
|
||||
if (fmt==NULL) return;
|
||||
if (!mi_recurse_enter()) return;
|
||||
vsnprintf(buf,sizeof(buf)-1,fmt,args);
|
||||
_mi_vsnprintf(buf, sizeof(buf)-1, fmt, args);
|
||||
mi_recurse_exit();
|
||||
_mi_fputs(out,arg,prefix,buf);
|
||||
}
|
||||
@@ -331,7 +489,7 @@ void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {
|
||||
static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {
|
||||
if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {
|
||||
char tprefix[64];
|
||||
snprintf(tprefix, sizeof(tprefix), "%sthread 0x%llx: ", prefix, (unsigned long long)_mi_thread_id());
|
||||
_mi_snprintf(tprefix, sizeof(tprefix), "%sthread 0x%tx: ", prefix, (uintptr_t)_mi_thread_id());
|
||||
mi_vfprintf(out, arg, tprefix, fmt, args);
|
||||
}
|
||||
else {
|
||||
@@ -339,6 +497,13 @@ static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_message(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void _mi_trace_message(const char* fmt, ...) {
|
||||
if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher
|
||||
va_list args;
|
||||
@@ -376,7 +541,7 @@ void _mi_warning_message(const char* fmt, ...) {
|
||||
|
||||
|
||||
#if MI_DEBUG
|
||||
void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) {
|
||||
mi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) mi_attr_noexcept {
|
||||
_mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);
|
||||
abort();
|
||||
}
|
||||
@@ -391,24 +556,27 @@ static _Atomic(void*) mi_error_arg; // = NULL
|
||||
|
||||
static void mi_error_default(int err) {
|
||||
MI_UNUSED(err);
|
||||
#if (MI_DEBUG>0)
|
||||
if (err==EFAULT) {
|
||||
#ifdef _MSC_VER
|
||||
__debugbreak();
|
||||
#endif
|
||||
abort();
|
||||
#if (MI_DEBUG>0)
|
||||
if (err==EFAULT) {
|
||||
#ifdef _MSC_VER
|
||||
__debugbreak();
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
#if (MI_SECURE>0)
|
||||
if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
#if defined(MI_XMALLOC)
|
||||
if (err==ENOMEM || err==EOVERFLOW || err==EINVAL) { // abort on memory allocation fails in xmalloc mode
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
if (errno==0) {
|
||||
errno = (err==EINVAL ? EINVAL : ENOMEM /* compatibility */ );
|
||||
}
|
||||
#endif
|
||||
#if (MI_SECURE>0)
|
||||
if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
#if defined(MI_XMALLOC)
|
||||
if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void mi_register_error(mi_error_fun* fun, void* arg) {
|
||||
@@ -422,7 +590,7 @@ void _mi_error_message(int err, const char* fmt, ...) {
|
||||
va_start(args, fmt);
|
||||
mi_show_error_message(fmt, args);
|
||||
va_end(args);
|
||||
// and call the error handler which may abort (or return normally)
|
||||
// and call the error handler which may abort (or return normally, potentially setting errno)
|
||||
if (mi_error_handler != NULL) {
|
||||
mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));
|
||||
}
|
||||
@@ -434,68 +602,6 @@ void _mi_error_message(int err, const char* fmt, ...) {
|
||||
// --------------------------------------------------------
|
||||
// Initialize options by checking the environment
|
||||
// --------------------------------------------------------
|
||||
char _mi_toupper(char c) {
|
||||
if (c >= 'a' && c <= 'z') return (c - 'a' + 'A');
|
||||
else return c;
|
||||
}
|
||||
|
||||
int _mi_strnicmp(const char* s, const char* t, size_t n) {
|
||||
if (n == 0) return 0;
|
||||
for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) {
|
||||
if (_mi_toupper(*s) != _mi_toupper(*t)) break;
|
||||
}
|
||||
return (n == 0 ? 0 : *s - *t);
|
||||
}
|
||||
|
||||
void _mi_strlcpy(char* dest, const char* src, size_t dest_size) {
|
||||
if (dest==NULL || src==NULL || dest_size == 0) return;
|
||||
// copy until end of src, or when dest is (almost) full
|
||||
while (*src != 0 && dest_size > 1) {
|
||||
*dest++ = *src++;
|
||||
dest_size--;
|
||||
}
|
||||
// always zero terminate
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
void _mi_strlcat(char* dest, const char* src, size_t dest_size) {
|
||||
if (dest==NULL || src==NULL || dest_size == 0) return;
|
||||
// find end of string in the dest buffer
|
||||
while (*dest != 0 && dest_size > 1) {
|
||||
dest++;
|
||||
dest_size--;
|
||||
}
|
||||
// and catenate
|
||||
_mi_strlcpy(dest, src, dest_size);
|
||||
}
|
||||
|
||||
size_t _mi_strlen(const char* s) {
|
||||
if (s==NULL) return 0;
|
||||
size_t len = 0;
|
||||
while(s[len] != 0) { len++; }
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t _mi_strnlen(const char* s, size_t max_len) {
|
||||
if (s==NULL) return 0;
|
||||
size_t len = 0;
|
||||
while(s[len] != 0 && len < max_len) { len++; }
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef MI_NO_GETENV
|
||||
static bool mi_getenv(const char* name, char* result, size_t result_size) {
|
||||
MI_UNUSED(name);
|
||||
MI_UNUSED(result);
|
||||
MI_UNUSED(result_size);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
static bool mi_getenv(const char* name, char* result, size_t result_size) {
|
||||
if (name==NULL || result == NULL || result_size < 64) return false;
|
||||
return _mi_prim_getenv(name,result,result_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: implement ourselves to reduce dependencies on the C runtime
|
||||
#include <stdlib.h> // strtol
|
||||
@@ -508,45 +614,51 @@ static void mi_option_init(mi_option_desc_t* desc) {
|
||||
char buf[64+1];
|
||||
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
|
||||
_mi_strlcat(buf, desc->name, sizeof(buf));
|
||||
bool found = mi_getenv(buf, s, sizeof(s));
|
||||
if (!found && desc->legacy_name != NULL) {
|
||||
int err = _mi_getenv(buf, s, sizeof(s));
|
||||
if (err==ENOENT && desc->legacy_name != NULL) {
|
||||
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
|
||||
_mi_strlcat(buf, desc->legacy_name, sizeof(buf));
|
||||
found = mi_getenv(buf, s, sizeof(s));
|
||||
if (found) {
|
||||
err = _mi_getenv(buf, s, sizeof(s));
|
||||
if (err==0) {
|
||||
_mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name);
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (err==0) {
|
||||
size_t len = _mi_strnlen(s, sizeof(buf) - 1);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
buf[i] = _mi_toupper(s[i]);
|
||||
}
|
||||
buf[len] = 0;
|
||||
if (buf[0] == 0 || strstr("1;TRUE;YES;ON", buf) != NULL) {
|
||||
if (buf[0] == 0 || _mi_streq(buf,"1") || _mi_streq(buf,"TRUE") || _mi_streq(buf,"YES") || _mi_streq(buf,"ON")) {
|
||||
desc->value = 1;
|
||||
desc->init = INITIALIZED;
|
||||
}
|
||||
else if (strstr("0;FALSE;NO;OFF", buf) != NULL) {
|
||||
else if (_mi_streq(buf,"0") || _mi_streq(buf,"FALSE") || _mi_streq(buf,"NO") || _mi_streq(buf,"OFF")) {
|
||||
desc->value = 0;
|
||||
desc->init = INITIALIZED;
|
||||
}
|
||||
else {
|
||||
char* end = buf;
|
||||
errno = 0;
|
||||
long value = strtol(buf, &end, 10);
|
||||
if (desc->option == mi_option_reserve_os_memory || desc->option == mi_option_arena_reserve) {
|
||||
// this option is interpreted in KiB to prevent overflow of `long`
|
||||
if (errno==0 && mi_option_has_size_in_kib(desc->option)) {
|
||||
// this option is interpreted in KiB to prevent overflow of `long` for large allocations
|
||||
// (long is 32-bit on 64-bit windows, which allows for 4TiB max.)
|
||||
size_t size = (value < 0 ? 0 : (size_t)value);
|
||||
bool overflow = false;
|
||||
if (*end == 'K') { end++; }
|
||||
else if (*end == 'M') { value *= MI_KiB; end++; }
|
||||
else if (*end == 'G') { value *= MI_MiB; end++; }
|
||||
else { value = (value + MI_KiB - 1) / MI_KiB; }
|
||||
if (end[0] == 'I' && end[1] == 'B') { end += 2; }
|
||||
else if (*end == 'B') { end++; }
|
||||
else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }
|
||||
else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }
|
||||
else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }
|
||||
else { size = (size + MI_KiB - 1) / MI_KiB; }
|
||||
if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB
|
||||
else if (*end == 'B') { end++; } // Kb, Mb, Gb, Tb
|
||||
if (overflow || size > (MI_MAX_ALLOC_SIZE / MI_KiB)) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }
|
||||
value = (size > LONG_MAX ? LONG_MAX : (long)size);
|
||||
}
|
||||
if (*end == 0) {
|
||||
desc->value = value;
|
||||
desc->init = INITIALIZED;
|
||||
if (errno==0 && *end == 0) {
|
||||
mi_option_set(desc->option, value);
|
||||
}
|
||||
else {
|
||||
// set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.
|
||||
@@ -565,7 +677,8 @@ static void mi_option_init(mi_option_desc_t* desc) {
|
||||
}
|
||||
mi_assert_internal(desc->init != UNINIT);
|
||||
}
|
||||
else if (!_mi_preloading()) {
|
||||
else if (err==ENOENT) {
|
||||
desc->init = DEFAULTED;
|
||||
}
|
||||
// and on another error, keep unitialized to try again (can happen during preloading if getenv is not available)
|
||||
}
|
||||
+257
-170
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2025, 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.
|
||||
@@ -9,20 +9,38 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h"
|
||||
|
||||
#define mi_os_stat_increase(stat,amount) _mi_stat_increase(&_mi_stats_main.stat, amount)
|
||||
#define mi_os_stat_decrease(stat,amount) _mi_stat_decrease(&_mi_stats_main.stat, amount)
|
||||
#define mi_os_stat_counter_increase(stat,inc) _mi_stat_counter_increase(&_mi_stats_main.stat, inc)
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Initialization.
|
||||
On windows initializes support for aligned allocation and
|
||||
large OS pages (if MIMALLOC_LARGE_OS_PAGES is true).
|
||||
----------------------------------------------------------- */
|
||||
#ifndef MI_DEFAULT_VIRTUAL_ADDRESS_BITS
|
||||
#if MI_INTPTR_SIZE < 8
|
||||
#define MI_DEFAULT_VIRTUAL_ADDRESS_BITS 32
|
||||
#else
|
||||
#define MI_DEFAULT_VIRTUAL_ADDRESS_BITS 48
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB
|
||||
#if MI_INTPTR_SIZE < 8
|
||||
#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB 4*MI_MiB // 4 GiB
|
||||
#else
|
||||
#define MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB 32*MI_MiB // 32 GiB
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static mi_os_mem_config_t mi_os_mem_config = {
|
||||
4096, // page size
|
||||
0, // large page size (usually 2MiB)
|
||||
4096, // allocation granularity
|
||||
true, // has overcommit? (if true we use MAP_NORESERVE on mmap systems)
|
||||
false, // must free whole? (on mmap systems we can free anywhere in a mapped range, but on Windows we must free the entire span)
|
||||
true // has virtual reserve? (if true we can reserve virtual address space without using commit or physical memory)
|
||||
4096, // page size
|
||||
0, // large page size (usually 2MiB)
|
||||
4096, // allocation granularity
|
||||
MI_DEFAULT_PHYSICAL_MEMORY_IN_KIB,
|
||||
MI_DEFAULT_VIRTUAL_ADDRESS_BITS,
|
||||
true, // has overcommit? (if true we use MAP_NORESERVE on mmap systems)
|
||||
false, // can we partially free allocated blocks? (on mmap systems we can free anywhere in a mapped range, but on Windows we must free the entire span)
|
||||
true // has virtual reserve? (if true we can reserve virtual address space without using commit or physical memory)
|
||||
};
|
||||
|
||||
bool _mi_os_has_overcommit(void) {
|
||||
@@ -44,9 +62,9 @@ size_t _mi_os_large_page_size(void) {
|
||||
return (mi_os_mem_config.large_page_size != 0 ? mi_os_mem_config.large_page_size : _mi_os_page_size());
|
||||
}
|
||||
|
||||
bool _mi_os_use_large_page(size_t size, size_t alignment) {
|
||||
bool _mi_os_canuse_large_page(size_t size, size_t alignment) {
|
||||
// if we have access, check the size and alignment requirements
|
||||
if (mi_os_mem_config.large_page_size == 0 || !mi_option_is_enabled(mi_option_allow_large_os_pages)) return false;
|
||||
if (mi_os_mem_config.large_page_size == 0) return false;
|
||||
return ((size % mi_os_mem_config.large_page_size) == 0 && (alignment % mi_os_mem_config.large_page_size) == 0);
|
||||
}
|
||||
|
||||
@@ -70,57 +88,46 @@ void _mi_os_init(void) {
|
||||
/* -----------------------------------------------------------
|
||||
Util
|
||||
-------------------------------------------------------------- */
|
||||
bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats);
|
||||
bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats);
|
||||
|
||||
static void* mi_align_up_ptr(void* p, size_t alignment) {
|
||||
return (void*)_mi_align_up((uintptr_t)p, alignment);
|
||||
}
|
||||
|
||||
static void* mi_align_down_ptr(void* p, size_t alignment) {
|
||||
return (void*)_mi_align_down((uintptr_t)p, alignment);
|
||||
}
|
||||
|
||||
bool _mi_os_decommit(void* addr, size_t size);
|
||||
bool _mi_os_commit(void* addr, size_t size, bool* is_zero);
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
aligned hinting
|
||||
-------------------------------------------------------------- */
|
||||
|
||||
// On 64-bit systems, we can do efficient aligned allocation by using
|
||||
// the 2TiB to 30TiB area to allocate those.
|
||||
#if (MI_INTPTR_SIZE >= 8)
|
||||
// On systems with enough virtual address bits, we can do efficient aligned allocation by using
|
||||
// the 2TiB to 30TiB area to allocate those. If we have at least 46 bits of virtual address
|
||||
// space (64TiB) we use this technique. (but see issue #939)
|
||||
#if (MI_INTPTR_SIZE >= 8) && !defined(MI_NO_ALIGNED_HINT)
|
||||
static mi_decl_cache_align _Atomic(uintptr_t)aligned_base;
|
||||
|
||||
// Return a MI_SEGMENT_SIZE aligned address that is probably available.
|
||||
// If this returns NULL, the OS will determine the address but on some OS's that may not be
|
||||
// properly aligned which can be more costly as it needs to be adjusted afterwards.
|
||||
// For a size > 1GiB this always returns NULL in order to guarantee good ASLR randomization;
|
||||
// In secure mode, for a size > 1GiB this always returns NULL in order to guarantee good ASLR randomization.
|
||||
// (otherwise an initial large allocation of say 2TiB has a 50% chance to include (known) addresses
|
||||
// in the middle of the 2TiB - 6TiB address range (see issue #372))
|
||||
|
||||
#define MI_HINT_BASE ((uintptr_t)2 << 40) // 2TiB start
|
||||
#define MI_HINT_AREA ((uintptr_t)4 << 40) // upto 6TiB (since before win8 there is "only" 8TiB available to processes)
|
||||
#define MI_HINT_AREA ((uintptr_t)4 << 40) // upto (2+4) 6TiB (since before win8 there is "only" 8TiB available to processes)
|
||||
#define MI_HINT_MAX ((uintptr_t)30 << 40) // wrap after 30TiB (area after 32TiB is used for huge OS pages)
|
||||
|
||||
void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size)
|
||||
{
|
||||
if (try_alignment <= 1 || try_alignment > MI_SEGMENT_SIZE) return NULL;
|
||||
if (mi_os_mem_config.virtual_address_bits < 46) return NULL; // < 64TiB virtual address space
|
||||
size = _mi_align_up(size, MI_SEGMENT_SIZE);
|
||||
if (size > 1*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<30) = 1/4096.
|
||||
#if (MI_SECURE>0)
|
||||
size += MI_SEGMENT_SIZE; // put in `MI_SEGMENT_SIZE` virtual gaps between hinted blocks; this splits VLA's but increases guarded areas.
|
||||
if (size > 1*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<30) = 1/4096.
|
||||
#endif
|
||||
size += MI_SEGMENT_SIZE; // put in `MI_SEGMENT_SIZE` virtual gaps between hinted blocks; this splits VLA's but increases guarded areas.
|
||||
|
||||
uintptr_t hint = mi_atomic_add_acq_rel(&aligned_base, size);
|
||||
if (hint == 0 || hint > MI_HINT_MAX) { // wrap or initialize
|
||||
uintptr_t init = MI_HINT_BASE;
|
||||
#if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of aligned allocations unless in debug mode
|
||||
mi_heap_t* heap = mi_prim_get_default_heap();
|
||||
// gh-123022: default heap may not be initialized in CPython in background threads
|
||||
if (mi_heap_is_initialized(heap)) {
|
||||
uintptr_t r = _mi_heap_random_next(heap);
|
||||
init = init + ((MI_SEGMENT_SIZE * ((r>>17) & 0xFFFFF)) % MI_HINT_AREA); // (randomly 20 bits)*4MiB == 0 to 4TiB
|
||||
}
|
||||
uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap());
|
||||
init = init + ((MI_SEGMENT_SIZE * ((r>>17) & 0xFFFFF)) % MI_HINT_AREA); // (randomly 20 bits)*4MiB == 0 to 4TiB
|
||||
#endif
|
||||
uintptr_t expected = hint + size;
|
||||
mi_atomic_cas_strong_acq_rel(&aligned_base, &expected, init);
|
||||
@@ -136,44 +143,51 @@ void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Free memory
|
||||
-------------------------------------------------------------- */
|
||||
|
||||
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_stats_t* stats);
|
||||
static void mi_os_free_huge_os_pages(void* p, size_t size);
|
||||
|
||||
static void mi_os_prim_free(void* addr, size_t size, bool still_committed, mi_stats_t* tld_stats) {
|
||||
MI_UNUSED(tld_stats);
|
||||
static void mi_os_prim_free(void* addr, size_t size, size_t commit_size) {
|
||||
mi_assert_internal((size % _mi_os_page_size()) == 0);
|
||||
if (addr == NULL || size == 0) return; // || _mi_os_is_huge_reserved(addr)
|
||||
int err = _mi_prim_free(addr, size);
|
||||
if (addr == NULL) return; // || _mi_os_is_huge_reserved(addr)
|
||||
int err = _mi_prim_free(addr, size); // allow size==0 (issue #1041)
|
||||
if (err != 0) {
|
||||
_mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr);
|
||||
}
|
||||
mi_stats_t* stats = &_mi_stats_main;
|
||||
if (still_committed) { _mi_stat_decrease(&stats->committed, size); }
|
||||
_mi_stat_decrease(&stats->reserved, size);
|
||||
if (commit_size > 0) {
|
||||
mi_os_stat_decrease(committed, commit_size);
|
||||
}
|
||||
mi_os_stat_decrease(reserved, size);
|
||||
}
|
||||
|
||||
void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid, mi_stats_t* tld_stats) {
|
||||
void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid) {
|
||||
if (mi_memkind_is_os(memid.memkind)) {
|
||||
size_t csize = _mi_os_good_alloc_size(size);
|
||||
size_t csize = memid.mem.os.size;
|
||||
if (csize==0) { csize = _mi_os_good_alloc_size(size); }
|
||||
mi_assert_internal(csize >= size);
|
||||
size_t commit_size = (still_committed ? csize : 0);
|
||||
void* base = addr;
|
||||
// different base? (due to alignment)
|
||||
if (memid.mem.os.base != NULL) {
|
||||
if (memid.mem.os.base != base) {
|
||||
mi_assert(memid.mem.os.base <= addr);
|
||||
mi_assert((uint8_t*)memid.mem.os.base + memid.mem.os.alignment >= (uint8_t*)addr);
|
||||
base = memid.mem.os.base;
|
||||
csize += ((uint8_t*)addr - (uint8_t*)memid.mem.os.base);
|
||||
const size_t diff = (uint8_t*)addr - (uint8_t*)memid.mem.os.base;
|
||||
if (memid.mem.os.size==0) {
|
||||
csize += diff;
|
||||
}
|
||||
if (still_committed) {
|
||||
commit_size -= diff; // the (addr-base) part was already un-committed
|
||||
}
|
||||
}
|
||||
// free it
|
||||
if (memid.memkind == MI_MEM_OS_HUGE) {
|
||||
mi_assert(memid.is_pinned);
|
||||
mi_os_free_huge_os_pages(base, csize, tld_stats);
|
||||
mi_os_free_huge_os_pages(base, csize);
|
||||
}
|
||||
else {
|
||||
mi_os_prim_free(base, csize, still_committed, tld_stats);
|
||||
mi_os_prim_free(base, csize, (still_committed ? commit_size : 0));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -182,8 +196,8 @@ void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t me
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_os_free(void* p, size_t size, mi_memid_t memid, mi_stats_t* tld_stats) {
|
||||
_mi_os_free_ex(p, size, true, memid, tld_stats);
|
||||
void _mi_os_free(void* p, size_t size, mi_memid_t memid) {
|
||||
_mi_os_free_ex(p, size, true, memid);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +206,8 @@ void _mi_os_free(void* p, size_t size, mi_memid_t memid, mi_stats_t* tld_stats)
|
||||
-------------------------------------------------------------- */
|
||||
|
||||
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
|
||||
static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, mi_stats_t* stats) {
|
||||
// Also `hint_addr` is a hint and may be ignored.
|
||||
static void* mi_os_prim_alloc_at(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
|
||||
mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
|
||||
mi_assert_internal(is_zero != NULL);
|
||||
mi_assert_internal(is_large != NULL);
|
||||
@@ -200,17 +215,26 @@ static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bo
|
||||
if (!commit) { allow_large = false; }
|
||||
if (try_alignment == 0) { try_alignment = 1; } // avoid 0 to ensure there will be no divide by zero when aligning
|
||||
|
||||
// try to align along large OS page size for larger allocations
|
||||
const size_t large_page_size = mi_os_mem_config.large_page_size;
|
||||
if (large_page_size > 0 && hint_addr == NULL && size >= 8*large_page_size && _mi_is_power_of_two(try_alignment) && try_alignment < large_page_size) {
|
||||
try_alignment = large_page_size;
|
||||
}
|
||||
|
||||
*is_zero = false;
|
||||
void* p = NULL;
|
||||
int err = _mi_prim_alloc(size, try_alignment, commit, allow_large, is_large, is_zero, &p);
|
||||
int err = _mi_prim_alloc(hint_addr, size, try_alignment, commit, allow_large, is_large, is_zero, &p);
|
||||
if (err != 0) {
|
||||
_mi_warning_message("unable to allocate OS memory (error: %d (0x%x), size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, size, try_alignment, commit, allow_large);
|
||||
_mi_warning_message("unable to allocate OS memory (error: %d (0x%x), addr: %p, size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, hint_addr, size, try_alignment, commit, allow_large);
|
||||
}
|
||||
mi_stat_counter_increase(stats->mmap_calls, 1);
|
||||
|
||||
|
||||
|
||||
mi_os_stat_counter_increase(mmap_calls, 1);
|
||||
if (p != NULL) {
|
||||
_mi_stat_increase(&stats->reserved, size);
|
||||
mi_os_stat_increase(reserved, size);
|
||||
if (commit) {
|
||||
_mi_stat_increase(&stats->committed, size);
|
||||
mi_os_stat_increase(committed, size);
|
||||
// seems needed for asan (or `mimalloc-test-api` fails)
|
||||
#ifdef MI_TRACK_ASAN
|
||||
if (*is_zero) { mi_track_mem_defined(p,size); }
|
||||
@@ -221,71 +245,86 @@ static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bo
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero) {
|
||||
return mi_os_prim_alloc_at(NULL, size, try_alignment, commit, allow_large, is_large, is_zero);
|
||||
}
|
||||
|
||||
|
||||
// Primitive aligned allocation from the OS.
|
||||
// This function guarantees the allocated memory is aligned.
|
||||
static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** base, mi_stats_t* stats) {
|
||||
static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid) {
|
||||
mi_assert_internal(memid!=NULL);
|
||||
mi_assert_internal(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0));
|
||||
mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
|
||||
mi_assert_internal(is_large != NULL);
|
||||
mi_assert_internal(is_zero != NULL);
|
||||
mi_assert_internal(base != NULL);
|
||||
_mi_memzero(memid,sizeof(*memid));
|
||||
if (!commit) allow_large = false;
|
||||
if (!(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0))) return NULL;
|
||||
size = _mi_align_up(size, _mi_os_page_size());
|
||||
|
||||
// try first with a hint (this will be aligned directly on Win 10+ or BSD)
|
||||
void* p = mi_os_prim_alloc(size, alignment, commit, allow_large, is_large, is_zero, stats);
|
||||
// try first with a requested alignment hint (this will usually be aligned directly on Win 10+ or BSD)
|
||||
bool os_is_large = false;
|
||||
bool os_is_zero = false;
|
||||
void* os_base = NULL;
|
||||
size_t os_size = size;
|
||||
void* p = mi_os_prim_alloc(size, alignment, commit, allow_large, &os_is_large, &os_is_zero);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
// aligned already?
|
||||
if (((uintptr_t)p % alignment) == 0) {
|
||||
*base = p;
|
||||
os_base = p;
|
||||
}
|
||||
else {
|
||||
// if not aligned, free it, overallocate, and unmap around it
|
||||
// NOTE(sgross): this warning causes issues in Python tests
|
||||
// _mi_warning_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit);
|
||||
mi_os_prim_free(p, size, commit, stats);
|
||||
#if !MI_TRACK_ASAN
|
||||
_mi_verbose_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit);
|
||||
#endif
|
||||
if (p != NULL) { mi_os_prim_free(p, size, (commit ? size : 0)); }
|
||||
if (size >= (SIZE_MAX - alignment)) return NULL; // overflow
|
||||
const size_t over_size = size + alignment;
|
||||
|
||||
if (mi_os_mem_config.must_free_whole) { // win32 virtualAlloc cannot free parts of an allocate block
|
||||
if (!mi_os_mem_config.has_partial_free) { // win32 virtualAlloc cannot free parts of an allocated block
|
||||
// over-allocate uncommitted (virtual) memory
|
||||
p = mi_os_prim_alloc(over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, is_large, is_zero, stats);
|
||||
p = mi_os_prim_alloc(over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, &os_is_large, &os_is_zero);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
// set p to the aligned part in the full region
|
||||
// note: this is dangerous on Windows as VirtualFree needs the actual base pointer
|
||||
// this is handled though by having the `base` field in the memid's
|
||||
*base = p; // remember the base
|
||||
p = mi_align_up_ptr(p, alignment);
|
||||
os_base = p; // remember the base
|
||||
os_size = size; // not over_size as otherwise we over-decrement commit stats on free
|
||||
p = _mi_align_up_ptr(p, alignment);
|
||||
|
||||
// explicitly commit only the aligned part
|
||||
if (commit) {
|
||||
_mi_os_commit(p, size, NULL, stats);
|
||||
if (!_mi_os_commit(p, size, NULL)) {
|
||||
mi_os_prim_free(os_base, over_size, 0);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // mmap can free inside an allocation
|
||||
// overallocate...
|
||||
p = mi_os_prim_alloc(over_size, 1, commit, false, is_large, is_zero, stats);
|
||||
p = mi_os_prim_alloc(over_size, 1, commit, false, &os_is_large, &os_is_zero);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
// and selectively unmap parts around the over-allocated area. (noop on sbrk)
|
||||
void* aligned_p = mi_align_up_ptr(p, alignment);
|
||||
size_t pre_size = (uint8_t*)aligned_p - (uint8_t*)p;
|
||||
size_t mid_size = _mi_align_up(size, _mi_os_page_size());
|
||||
size_t post_size = over_size - pre_size - mid_size;
|
||||
// and selectively unmap parts around the over-allocated area.
|
||||
void* const aligned_p = _mi_align_up_ptr(p, alignment);
|
||||
const size_t pre_size = (uint8_t*)aligned_p - (uint8_t*)p;
|
||||
const size_t mid_size = _mi_align_up(size, _mi_os_page_size());
|
||||
const size_t post_size = over_size - pre_size - mid_size;
|
||||
mi_assert_internal(pre_size < over_size&& post_size < over_size&& mid_size >= size);
|
||||
if (pre_size > 0) { mi_os_prim_free(p, pre_size, commit, stats); }
|
||||
if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, commit, stats); }
|
||||
// we can return the aligned pointer on `mmap` (and sbrk) systems
|
||||
if (pre_size > 0) { mi_os_prim_free(p, pre_size, (commit ? pre_size : 0)); }
|
||||
if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, (commit ? post_size : 0)); }
|
||||
// we can return the aligned pointer on `mmap` systems
|
||||
p = aligned_p;
|
||||
*base = aligned_p; // since we freed the pre part, `*base == p`.
|
||||
os_base = aligned_p; // since we freed the pre part, `*base == p`.
|
||||
os_size = mid_size;
|
||||
}
|
||||
}
|
||||
|
||||
mi_assert_internal(p == NULL || (p != NULL && *base != NULL && ((uintptr_t)p % alignment) == 0));
|
||||
mi_assert_internal(p != NULL && os_base != NULL && _mi_is_aligned(p,alignment));
|
||||
mi_assert_internal(os_base <= p && size <= os_size);
|
||||
*memid = _mi_memid_create_os(os_base,os_size,commit,os_is_zero,os_is_large);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -294,72 +333,94 @@ static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit
|
||||
OS API: alloc and alloc_aligned
|
||||
----------------------------------------------------------- */
|
||||
|
||||
void* _mi_os_alloc(size_t size, mi_memid_t* memid, mi_stats_t* tld_stats) {
|
||||
MI_UNUSED(tld_stats);
|
||||
void* _mi_os_alloc(size_t size, mi_memid_t* memid) {
|
||||
*memid = _mi_memid_none();
|
||||
mi_stats_t* stats = &_mi_stats_main;
|
||||
if (size == 0) return NULL;
|
||||
size = _mi_os_good_alloc_size(size);
|
||||
bool os_is_large = false;
|
||||
bool os_is_zero = false;
|
||||
void* p = mi_os_prim_alloc(size, 0, true, false, &os_is_large, &os_is_zero, stats);
|
||||
if (p != NULL) {
|
||||
*memid = _mi_memid_create_os(true, os_is_zero, os_is_large);
|
||||
}
|
||||
void* p = mi_os_prim_alloc(size, 0, true, false, &os_is_large, &os_is_zero);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
*memid = _mi_memid_create_os(p, size, true, os_is_zero, os_is_large);
|
||||
mi_assert_internal(memid->mem.os.size >= size);
|
||||
mi_assert_internal(memid->initially_committed);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* tld_stats)
|
||||
void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid)
|
||||
{
|
||||
MI_UNUSED(&_mi_os_get_aligned_hint); // suppress unused warnings
|
||||
MI_UNUSED(tld_stats);
|
||||
*memid = _mi_memid_none();
|
||||
if (size == 0) return NULL;
|
||||
size = _mi_os_good_alloc_size(size);
|
||||
alignment = _mi_align_up(alignment, _mi_os_page_size());
|
||||
|
||||
bool os_is_large = false;
|
||||
bool os_is_zero = false;
|
||||
void* os_base = NULL;
|
||||
void* p = mi_os_prim_alloc_aligned(size, alignment, commit, allow_large, &os_is_large, &os_is_zero, &os_base, &_mi_stats_main /*tld->stats*/ );
|
||||
if (p != NULL) {
|
||||
*memid = _mi_memid_create_os(commit, os_is_zero, os_is_large);
|
||||
memid->mem.os.base = os_base;
|
||||
memid->mem.os.alignment = alignment;
|
||||
}
|
||||
void* p = mi_os_prim_alloc_aligned(size, alignment, commit, allow_large, memid );
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
mi_assert_internal(memid->mem.os.size >= size);
|
||||
mi_assert_internal(_mi_is_aligned(p,alignment));
|
||||
if (commit) { mi_assert_internal(memid->initially_committed); }
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
mi_decl_nodiscard static void* mi_os_ensure_zero(void* p, size_t size, mi_memid_t* memid) {
|
||||
if (p==NULL || size==0) return p;
|
||||
// ensure committed
|
||||
if (!memid->initially_committed) {
|
||||
bool is_zero = false;
|
||||
if (!_mi_os_commit(p, size, &is_zero)) {
|
||||
_mi_os_free(p, size, *memid);
|
||||
return NULL;
|
||||
}
|
||||
memid->initially_committed = true;
|
||||
}
|
||||
// ensure zero'd
|
||||
if (memid->initially_zero) return p;
|
||||
_mi_memzero_aligned(p,size);
|
||||
memid->initially_zero = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
void* _mi_os_zalloc(size_t size, mi_memid_t* memid) {
|
||||
void* p = _mi_os_alloc(size,memid);
|
||||
return mi_os_ensure_zero(p, size, memid);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
OS aligned allocation with an offset. This is used
|
||||
for large alignments > MI_ALIGNMENT_MAX. We use a large mimalloc
|
||||
for large alignments > MI_BLOCK_ALIGNMENT_MAX. We use a large mimalloc
|
||||
page where the object can be aligned at an offset from the start of the segment.
|
||||
As we may need to overallocate, we need to free such pointers using `mi_free_aligned`
|
||||
to use the actual start of the memory region.
|
||||
----------------------------------------------------------- */
|
||||
|
||||
void* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* tld_stats) {
|
||||
void* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid) {
|
||||
mi_assert(offset <= MI_SEGMENT_SIZE);
|
||||
mi_assert(offset <= size);
|
||||
mi_assert((alignment % _mi_os_page_size()) == 0);
|
||||
*memid = _mi_memid_none();
|
||||
if (offset > MI_SEGMENT_SIZE) return NULL;
|
||||
if (offset > MI_SEGMENT_SIZE || offset > size) return NULL;
|
||||
if (offset == 0) {
|
||||
// regular aligned allocation
|
||||
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid, tld_stats);
|
||||
return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid);
|
||||
}
|
||||
else {
|
||||
// overallocate to align at an offset
|
||||
const size_t extra = _mi_align_up(offset, alignment) - offset;
|
||||
if (size >= SIZE_MAX - extra) return NULL; // too large
|
||||
const size_t oversize = size + extra;
|
||||
void* const start = _mi_os_alloc_aligned(oversize, alignment, commit, allow_large, memid, tld_stats);
|
||||
void* const start = _mi_os_alloc_aligned(oversize, alignment, commit, allow_large, memid);
|
||||
if (start == NULL) return NULL;
|
||||
|
||||
void* const p = (uint8_t*)start + extra;
|
||||
mi_assert(_mi_is_aligned((uint8_t*)p + offset, alignment));
|
||||
// decommit the overallocation at the start
|
||||
if (commit && extra > _mi_os_page_size()) {
|
||||
_mi_os_decommit(start, extra, tld_stats);
|
||||
// note: this double counts the decommit when freeing `memid`. Should we keep commit size in the memid as well?
|
||||
if (commit && extra >= _mi_os_page_size()) {
|
||||
_mi_os_decommit(start, extra);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@@ -377,10 +438,10 @@ static void* mi_os_page_align_areax(bool conservative, void* addr, size_t size,
|
||||
if (size == 0 || addr == NULL) return NULL;
|
||||
|
||||
// page align conservatively within the range
|
||||
void* start = (conservative ? mi_align_up_ptr(addr, _mi_os_page_size())
|
||||
: mi_align_down_ptr(addr, _mi_os_page_size()));
|
||||
void* end = (conservative ? mi_align_down_ptr((uint8_t*)addr + size, _mi_os_page_size())
|
||||
: mi_align_up_ptr((uint8_t*)addr + size, _mi_os_page_size()));
|
||||
void* start = (conservative ? _mi_align_up_ptr(addr, _mi_os_page_size())
|
||||
: _mi_align_down_ptr(addr, _mi_os_page_size()));
|
||||
void* end = (conservative ? _mi_align_down_ptr((uint8_t*)addr + size, _mi_os_page_size())
|
||||
: _mi_align_up_ptr((uint8_t*)addr + size, _mi_os_page_size()));
|
||||
ptrdiff_t diff = (uint8_t*)end - (uint8_t*)start;
|
||||
if (diff <= 0) return NULL;
|
||||
|
||||
@@ -393,12 +454,9 @@ static void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t*
|
||||
return mi_os_page_align_areax(true, addr, size, newsize);
|
||||
}
|
||||
|
||||
bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats) {
|
||||
MI_UNUSED(tld_stats);
|
||||
mi_stats_t* stats = &_mi_stats_main;
|
||||
bool _mi_os_commit_ex(void* addr, size_t size, bool* is_zero, size_t stat_size) {
|
||||
if (is_zero != NULL) { *is_zero = false; }
|
||||
_mi_stat_increase(&stats->committed, size); // use size for precise commit vs. decommit
|
||||
_mi_stat_counter_increase(&stats->commit_calls, 1);
|
||||
mi_os_stat_counter_increase(commit_calls, 1);
|
||||
|
||||
// page align range
|
||||
size_t csize;
|
||||
@@ -421,14 +479,16 @@ bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats
|
||||
if (os_is_zero) { mi_track_mem_defined(start,csize); }
|
||||
else { mi_track_mem_undefined(start,csize); }
|
||||
#endif
|
||||
mi_os_stat_increase(committed, stat_size); // use size for precise commit vs. decommit
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, mi_stats_t* tld_stats) {
|
||||
MI_UNUSED(tld_stats);
|
||||
mi_stats_t* stats = &_mi_stats_main;
|
||||
bool _mi_os_commit(void* addr, size_t size, bool* is_zero) {
|
||||
return _mi_os_commit_ex(addr, size, is_zero, size);
|
||||
}
|
||||
|
||||
static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, size_t stat_size) {
|
||||
mi_assert_internal(needs_recommit!=NULL);
|
||||
_mi_stat_decrease(&stats->committed, size);
|
||||
|
||||
// page align
|
||||
size_t csize;
|
||||
@@ -441,13 +501,16 @@ static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, mi_
|
||||
if (err != 0) {
|
||||
_mi_warning_message("cannot decommit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize);
|
||||
}
|
||||
else if (*needs_recommit) {
|
||||
mi_os_stat_decrease(committed, stat_size);
|
||||
}
|
||||
mi_assert_internal(err == 0);
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* tld_stats) {
|
||||
bool _mi_os_decommit(void* addr, size_t size) {
|
||||
bool needs_recommit;
|
||||
return mi_os_decommit_ex(addr, size, &needs_recommit, tld_stats);
|
||||
return mi_os_decommit_ex(addr, size, &needs_recommit, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -455,13 +518,13 @@ bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* tld_stats) {
|
||||
// but may be used later again. This will release physical memory
|
||||
// pages and reduce swapping while keeping the memory committed.
|
||||
// We page align to a conservative area inside the range to reset.
|
||||
bool _mi_os_reset(void* addr, size_t size, mi_stats_t* stats) {
|
||||
bool _mi_os_reset(void* addr, size_t size) {
|
||||
// page align conservatively within the range
|
||||
size_t csize;
|
||||
void* start = mi_os_page_align_area_conservative(addr, size, &csize);
|
||||
if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr)
|
||||
_mi_stat_increase(&stats->reset, csize);
|
||||
_mi_stat_counter_increase(&stats->reset_calls, 1);
|
||||
mi_os_stat_counter_increase(reset, csize);
|
||||
mi_os_stat_counter_increase(reset_calls, 1);
|
||||
|
||||
#if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN
|
||||
memset(start, 0, csize); // pretend it is eagerly reset
|
||||
@@ -475,24 +538,35 @@ bool _mi_os_reset(void* addr, size_t size, mi_stats_t* stats) {
|
||||
}
|
||||
|
||||
|
||||
void _mi_os_reuse( void* addr, size_t size ) {
|
||||
// page align conservatively within the range
|
||||
size_t csize = 0;
|
||||
void* const start = mi_os_page_align_area_conservative(addr, size, &csize);
|
||||
if (csize == 0) return;
|
||||
const int err = _mi_prim_reuse(start, csize);
|
||||
if (err != 0) {
|
||||
_mi_warning_message("cannot reuse OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize);
|
||||
}
|
||||
}
|
||||
|
||||
// either resets or decommits memory, returns true if the memory needs
|
||||
// to be recommitted if it is to be re-used later on.
|
||||
bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, mi_stats_t* stats)
|
||||
bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, size_t stat_size)
|
||||
{
|
||||
if (mi_option_get(mi_option_purge_delay) < 0) return false; // is purging allowed?
|
||||
_mi_stat_counter_increase(&stats->purge_calls, 1);
|
||||
_mi_stat_increase(&stats->purged, size);
|
||||
mi_os_stat_counter_increase(purge_calls, 1);
|
||||
mi_os_stat_counter_increase(purged, size);
|
||||
|
||||
if (mi_option_is_enabled(mi_option_purge_decommits) && // should decommit?
|
||||
!_mi_preloading()) // don't decommit during preloading (unsafe)
|
||||
{
|
||||
bool needs_recommit = true;
|
||||
mi_os_decommit_ex(p, size, &needs_recommit, stats);
|
||||
mi_os_decommit_ex(p, size, &needs_recommit, stat_size);
|
||||
return needs_recommit;
|
||||
}
|
||||
else {
|
||||
if (allow_reset) { // this can sometimes be not allowed if the range is not fully committed
|
||||
_mi_os_reset(p, size, stats);
|
||||
_mi_os_reset(p, size);
|
||||
}
|
||||
return false; // needs no recommit
|
||||
}
|
||||
@@ -500,8 +574,8 @@ bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, mi_stats_t* stats)
|
||||
|
||||
// either resets or decommits memory, returns true if the memory needs
|
||||
// to be recommitted if it is to be re-used later on.
|
||||
bool _mi_os_purge(void* p, size_t size, mi_stats_t * stats) {
|
||||
return _mi_os_purge_ex(p, size, true, stats);
|
||||
bool _mi_os_purge(void* p, size_t size) {
|
||||
return _mi_os_purge_ex(p, size, true, size);
|
||||
}
|
||||
|
||||
// Protect a region in memory to be not accessible.
|
||||
@@ -512,7 +586,7 @@ static bool mi_os_protectx(void* addr, size_t size, bool protect) {
|
||||
if (csize == 0) return false;
|
||||
/*
|
||||
if (_mi_os_is_huge_reserved(addr)) {
|
||||
_mi_warning_message("cannot mprotect memory allocated in huge OS pages\n");
|
||||
_mi_warning_message("cannot mprotect memory allocated in huge OS pages\n");
|
||||
}
|
||||
*/
|
||||
int err = _mi_prim_protect(start,csize,protect);
|
||||
@@ -546,7 +620,11 @@ static mi_decl_cache_align _Atomic(uintptr_t) mi_huge_start; // = 0
|
||||
// Claim an aligned address range for huge pages
|
||||
static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
|
||||
if (total_size != NULL) *total_size = 0;
|
||||
const size_t size = pages * MI_HUGE_OS_PAGE_SIZE;
|
||||
size_t size = 0;
|
||||
if (mi_mul_overflow(pages,MI_HUGE_OS_PAGE_SIZE,&size)) {
|
||||
_mi_warning_message("too many huge pages requested: %zu\n", pages);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uintptr_t start = 0;
|
||||
uintptr_t end = 0;
|
||||
@@ -556,14 +634,10 @@ static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) {
|
||||
if (start == 0) {
|
||||
// Initialize the start address after the 32TiB area
|
||||
start = ((uintptr_t)32 << 40); // 32TiB virtual start address
|
||||
#if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode
|
||||
mi_heap_t* heap = mi_prim_get_default_heap();
|
||||
// gh-123022: default heap may not be initialized in CPython in background threads
|
||||
if (mi_heap_is_initialized(heap)) {
|
||||
uintptr_t r = _mi_heap_random_next(heap);
|
||||
start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF)); // (randomly 12bits)*1GiB == between 0 to 4TiB
|
||||
}
|
||||
#endif
|
||||
#if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode
|
||||
uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap());
|
||||
start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF)); // (randomly 12bits)*1GiB == between 0 to 4TiB
|
||||
#endif
|
||||
}
|
||||
end = start + size;
|
||||
mi_assert_internal(end % MI_SEGMENT_SIZE == 0);
|
||||
@@ -586,7 +660,7 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
|
||||
if (psize != NULL) *psize = 0;
|
||||
if (pages_reserved != NULL) *pages_reserved = 0;
|
||||
size_t size = 0;
|
||||
uint8_t* start = mi_os_claim_huge_pages(pages, &size);
|
||||
uint8_t* const start = mi_os_claim_huge_pages(pages, &size);
|
||||
if (start == NULL) return NULL; // or 32-bit systems
|
||||
|
||||
// Allocate one page at the time but try to place them contiguously
|
||||
@@ -612,21 +686,21 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
|
||||
// no success, issue a warning and break
|
||||
if (p != NULL) {
|
||||
_mi_warning_message("could not allocate contiguous huge OS page %zu at %p\n", page, addr);
|
||||
mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, true, &_mi_stats_main);
|
||||
mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// success, record it
|
||||
page++; // increase before timeout check (see issue #711)
|
||||
_mi_stat_increase(&_mi_stats_main.committed, MI_HUGE_OS_PAGE_SIZE);
|
||||
_mi_stat_increase(&_mi_stats_main.reserved, MI_HUGE_OS_PAGE_SIZE);
|
||||
mi_os_stat_increase(committed, MI_HUGE_OS_PAGE_SIZE);
|
||||
mi_os_stat_increase(reserved, MI_HUGE_OS_PAGE_SIZE);
|
||||
|
||||
// check for timeout
|
||||
if (max_msecs > 0) {
|
||||
mi_msecs_t elapsed = _mi_clock_end(start_t);
|
||||
if (page >= 1) {
|
||||
mi_msecs_t estimate = ((elapsed / (page+1)) * pages);
|
||||
mi_msecs_t estimate = ((elapsed / (page==0 ? 1 : page)) * pages);
|
||||
if (estimate > 2*max_msecs) { // seems like we are going to timeout, break
|
||||
elapsed = max_msecs + 1;
|
||||
}
|
||||
@@ -637,16 +711,17 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
|
||||
}
|
||||
}
|
||||
}
|
||||
mi_assert_internal(page*MI_HUGE_OS_PAGE_SIZE <= size);
|
||||
const size_t allocated = page * MI_HUGE_OS_PAGE_SIZE;
|
||||
mi_assert_internal(allocated <= size);
|
||||
if (pages_reserved != NULL) { *pages_reserved = page; }
|
||||
if (psize != NULL) { *psize = page * MI_HUGE_OS_PAGE_SIZE; }
|
||||
if (psize != NULL) { *psize = allocated; }
|
||||
if (page != 0) {
|
||||
mi_assert(start != NULL);
|
||||
*memid = _mi_memid_create_os(true /* is committed */, all_zero, true /* is_large */);
|
||||
*memid = _mi_memid_create_os(start, allocated, true /* is committed */, all_zero, true /* is_large */);
|
||||
memid->memkind = MI_MEM_OS_HUGE;
|
||||
mi_assert(memid->is_pinned);
|
||||
#ifdef MI_TRACK_ASAN
|
||||
if (all_zero) { mi_track_mem_defined(start,size); }
|
||||
if (all_zero) { mi_track_mem_defined(start,allocated); }
|
||||
#endif
|
||||
}
|
||||
return (page == 0 ? NULL : start);
|
||||
@@ -654,45 +729,57 @@ void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_mse
|
||||
|
||||
// free every huge page in a range individually (as we allocated per page)
|
||||
// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems.
|
||||
static void mi_os_free_huge_os_pages(void* p, size_t size, mi_stats_t* stats) {
|
||||
static void mi_os_free_huge_os_pages(void* p, size_t size) {
|
||||
if (p==NULL || size==0) return;
|
||||
uint8_t* base = (uint8_t*)p;
|
||||
while (size >= MI_HUGE_OS_PAGE_SIZE) {
|
||||
mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, true, stats);
|
||||
mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, MI_HUGE_OS_PAGE_SIZE);
|
||||
size -= MI_HUGE_OS_PAGE_SIZE;
|
||||
base += MI_HUGE_OS_PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
Support NUMA aware allocation
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
_Atomic(size_t) _mi_numa_node_count; // = 0 // cache the node count
|
||||
static _Atomic(size_t) mi_numa_node_count; // = 0 // cache the node count
|
||||
|
||||
size_t _mi_os_numa_node_count_get(void) {
|
||||
size_t count = mi_atomic_load_acquire(&_mi_numa_node_count);
|
||||
if (count <= 0) {
|
||||
int _mi_os_numa_node_count(void) {
|
||||
size_t count = mi_atomic_load_acquire(&mi_numa_node_count);
|
||||
if mi_unlikely(count == 0) {
|
||||
long ncount = mi_option_get(mi_option_use_numa_nodes); // given explicitly?
|
||||
if (ncount > 0) {
|
||||
if (ncount > 0 && ncount < INT_MAX) {
|
||||
count = (size_t)ncount;
|
||||
}
|
||||
else {
|
||||
count = _mi_prim_numa_node_count(); // or detect dynamically
|
||||
if (count == 0) count = 1;
|
||||
const size_t n = _mi_prim_numa_node_count(); // or detect dynamically
|
||||
if (n == 0 || n > INT_MAX) { count = 1; }
|
||||
else { count = n; }
|
||||
}
|
||||
mi_atomic_store_release(&_mi_numa_node_count, count); // save it
|
||||
mi_atomic_store_release(&mi_numa_node_count, count); // save it
|
||||
_mi_verbose_message("using %zd numa regions\n", count);
|
||||
}
|
||||
return count;
|
||||
mi_assert_internal(count > 0 && count <= INT_MAX);
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
int _mi_os_numa_node_get(mi_os_tld_t* tld) {
|
||||
MI_UNUSED(tld);
|
||||
size_t numa_count = _mi_os_numa_node_count();
|
||||
static int mi_os_numa_node_get(void) {
|
||||
int numa_count = _mi_os_numa_node_count();
|
||||
if (numa_count<=1) return 0; // optimize on single numa node systems: always node 0
|
||||
// never more than the node count and >= 0
|
||||
size_t numa_node = _mi_prim_numa_node();
|
||||
const size_t n = _mi_prim_numa_node();
|
||||
int numa_node = (n < INT_MAX ? (int)n : 0);
|
||||
if (numa_node >= numa_count) { numa_node = numa_node % numa_count; }
|
||||
return (int)numa_node;
|
||||
return numa_node;
|
||||
}
|
||||
|
||||
int _mi_os_numa_node(void) {
|
||||
if mi_likely(mi_atomic_load_relaxed(&mi_numa_node_count) == 1) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return mi_os_numa_node_get();
|
||||
}
|
||||
}
|
||||
+163
-64
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2020, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2024, 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.
|
||||
@@ -11,6 +11,10 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
|
||||
#ifndef MI_IN_PAGE_C
|
||||
#error "this file should be included from 'page.c'"
|
||||
// include to help an IDE
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
#endif
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
@@ -53,27 +57,23 @@ static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) {
|
||||
// Returns MI_BIN_HUGE if the size is too large.
|
||||
// We use `wsize` for the size in "machine word sizes",
|
||||
// i.e. byte size == `wsize*sizeof(void*)`.
|
||||
static inline uint8_t mi_bin(size_t size) {
|
||||
static inline size_t mi_bin(size_t size) {
|
||||
size_t wsize = _mi_wsize_from_size(size);
|
||||
uint8_t bin;
|
||||
if (wsize <= 1) {
|
||||
bin = 1;
|
||||
#if defined(MI_ALIGN4W)
|
||||
if mi_likely(wsize <= 4) {
|
||||
return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes
|
||||
}
|
||||
#if defined(MI_ALIGN4W)
|
||||
else if (wsize <= 4) {
|
||||
bin = (uint8_t)((wsize+1)&~1); // round to double word sizes
|
||||
#elif defined(MI_ALIGN2W)
|
||||
if mi_likely(wsize <= 8) {
|
||||
return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes
|
||||
}
|
||||
#elif defined(MI_ALIGN2W)
|
||||
else if (wsize <= 8) {
|
||||
bin = (uint8_t)((wsize+1)&~1); // round to double word sizes
|
||||
#else
|
||||
if mi_likely(wsize <= 8) {
|
||||
return (wsize == 0 ? 1 : wsize);
|
||||
}
|
||||
#else
|
||||
else if (wsize <= 8) {
|
||||
bin = (uint8_t)wsize;
|
||||
}
|
||||
#endif
|
||||
else if (wsize > MI_MEDIUM_OBJ_WSIZE_MAX) {
|
||||
bin = MI_BIN_HUGE;
|
||||
#endif
|
||||
else if mi_unlikely(wsize > MI_MEDIUM_OBJ_WSIZE_MAX) {
|
||||
return MI_BIN_HUGE;
|
||||
}
|
||||
else {
|
||||
#if defined(MI_ALIGN4W)
|
||||
@@ -81,15 +81,14 @@ static inline uint8_t mi_bin(size_t size) {
|
||||
#endif
|
||||
wsize--;
|
||||
// find the highest bit
|
||||
uint8_t b = (uint8_t)mi_bsr(wsize); // note: wsize != 0
|
||||
const size_t b = (MI_SIZE_BITS - 1 - mi_clz(wsize)); // note: wsize != 0
|
||||
// and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation).
|
||||
// - adjust with 3 because we use do not round the first 8 sizes
|
||||
// which each get an exact bin
|
||||
bin = ((b << 2) + (uint8_t)((wsize >> (b - 2)) & 0x03)) - 3;
|
||||
mi_assert_internal(bin < MI_BIN_HUGE);
|
||||
const size_t bin = ((b << 2) + ((wsize >> (b - 2)) & 0x03)) - 3;
|
||||
mi_assert_internal(bin > 0 && bin < MI_BIN_HUGE);
|
||||
return bin;
|
||||
}
|
||||
mi_assert_internal(bin > 0 && bin <= MI_BIN_HUGE);
|
||||
return bin;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,21 +97,24 @@ static inline uint8_t mi_bin(size_t size) {
|
||||
Queue of pages with free blocks
|
||||
----------------------------------------------------------- */
|
||||
|
||||
uint8_t _mi_bin(size_t size) {
|
||||
size_t _mi_bin(size_t size) {
|
||||
return mi_bin(size);
|
||||
}
|
||||
|
||||
size_t _mi_bin_size(uint8_t bin) {
|
||||
size_t _mi_bin_size(size_t bin) {
|
||||
return _mi_heap_empty.pages[bin].block_size;
|
||||
}
|
||||
|
||||
// Good size for allocation
|
||||
size_t mi_good_size(size_t size) mi_attr_noexcept {
|
||||
if (size <= MI_MEDIUM_OBJ_SIZE_MAX) {
|
||||
return _mi_bin_size(mi_bin(size));
|
||||
if (size <= MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) {
|
||||
return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE));
|
||||
}
|
||||
else if (size <= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) {
|
||||
return _mi_align_up(size + MI_PADDING_SIZE,_mi_os_page_size());
|
||||
}
|
||||
else {
|
||||
return _mi_align_up(size,_mi_os_page_size());
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,21 +139,37 @@ static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t*
|
||||
}
|
||||
#endif
|
||||
|
||||
static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) {
|
||||
uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : mi_bin(page->xblock_size));
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
mi_assert_internal(heap != NULL && bin <= MI_BIN_FULL);
|
||||
mi_page_queue_t* pq = &heap->pages[bin];
|
||||
mi_assert_internal(bin >= MI_BIN_HUGE || page->xblock_size == pq->block_size);
|
||||
mi_assert_expensive(mi_page_queue_contains(pq, page));
|
||||
return pq;
|
||||
static inline bool mi_page_is_large_or_huge(const mi_page_t* page) {
|
||||
return (mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_huge(page));
|
||||
}
|
||||
|
||||
static size_t mi_page_bin(const mi_page_t* page) {
|
||||
const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
|
||||
mi_assert_internal(bin <= MI_BIN_FULL);
|
||||
return bin;
|
||||
}
|
||||
|
||||
// returns the page bin without using MI_BIN_FULL for statistics
|
||||
size_t _mi_page_stats_bin(const mi_page_t* page) {
|
||||
const size_t bin = (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page)));
|
||||
mi_assert_internal(bin <= MI_BIN_HUGE);
|
||||
return bin;
|
||||
}
|
||||
|
||||
static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) {
|
||||
uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : mi_bin(page->xblock_size));
|
||||
mi_assert_internal(bin <= MI_BIN_FULL);
|
||||
mi_assert_internal(heap!=NULL);
|
||||
const size_t bin = mi_page_bin(page);
|
||||
mi_page_queue_t* pq = &heap->pages[bin];
|
||||
mi_assert_internal(mi_page_is_in_full(page) || page->xblock_size == pq->block_size);
|
||||
mi_assert_internal((mi_page_block_size(page) == pq->block_size) ||
|
||||
(mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(pq)) ||
|
||||
(mi_page_is_in_full(page) && mi_page_queue_is_full(pq)));
|
||||
return pq;
|
||||
}
|
||||
|
||||
static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) {
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
|
||||
mi_assert_expensive(mi_page_queue_contains(pq, page));
|
||||
return pq;
|
||||
}
|
||||
|
||||
@@ -162,26 +180,26 @@ static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t*
|
||||
// range of entries in `_mi_page_small_free`.
|
||||
static inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) {
|
||||
mi_assert_internal(mi_heap_contains_queue(heap,pq));
|
||||
size_t size = pq->block_size;
|
||||
const size_t size = pq->block_size;
|
||||
if (size > MI_SMALL_SIZE_MAX) return;
|
||||
|
||||
mi_page_t* page = pq->first;
|
||||
if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty;
|
||||
|
||||
// find index in the right direct page array
|
||||
size_t start;
|
||||
size_t idx = _mi_wsize_from_size(size);
|
||||
mi_page_t** pages_free = heap->pages_free_direct;
|
||||
|
||||
const size_t idx = _mi_wsize_from_size(size);
|
||||
mi_page_t** const pages_free = heap->pages_free_direct;
|
||||
if (pages_free[idx] == page) return; // already set
|
||||
|
||||
// find start slot
|
||||
size_t start;
|
||||
if (idx<=1) {
|
||||
start = 0;
|
||||
}
|
||||
else {
|
||||
// find previous size; due to minimal alignment upto 3 previous bins may need to be skipped
|
||||
uint8_t bin = mi_bin(size);
|
||||
mi_assert_internal(pq > &heap->pages[0]); // since idx > 1
|
||||
size_t bin = mi_bin(size);
|
||||
const mi_page_queue_t* prev = pq - 1;
|
||||
while( bin == mi_bin(prev->block_size) && prev > &heap->pages[0]) {
|
||||
prev--;
|
||||
@@ -206,7 +224,9 @@ static bool mi_page_queue_is_empty(mi_page_queue_t* queue) {
|
||||
static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
|
||||
mi_assert_internal(page != NULL);
|
||||
mi_assert_expensive(mi_page_queue_contains(queue, page));
|
||||
mi_assert_internal(page->xblock_size == queue->block_size || (page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
|
||||
mi_assert_internal(mi_page_block_size(page) == queue->block_size ||
|
||||
(mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||
|
||||
(mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
|
||||
if (page->prev != NULL) page->prev->next = page->next;
|
||||
@@ -226,14 +246,18 @@ static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
|
||||
}
|
||||
|
||||
|
||||
#if MI_DEBUG >= 3
|
||||
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue);
|
||||
#endif
|
||||
|
||||
static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
|
||||
mi_assert_internal(mi_page_heap(page) == heap);
|
||||
mi_assert_internal(!mi_page_queue_contains(queue, page));
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
|
||||
#endif
|
||||
mi_assert_internal(page->xblock_size == queue->block_size ||
|
||||
(page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX) ||
|
||||
mi_assert_internal(mi_page_block_size(page) == queue->block_size ||
|
||||
(mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||
|
||||
(mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
|
||||
|
||||
mi_page_set_in_full(page, mi_page_queue_is_full(queue));
|
||||
@@ -252,21 +276,59 @@ static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_
|
||||
// update direct
|
||||
mi_heap_queue_first_update(heap, queue);
|
||||
heap->page_count++;
|
||||
|
||||
// [specbot Q-NEW-1] first/last must be both null or both non-null (L1, O(1))
|
||||
mi_assert_internal((queue->first == NULL) == (queue->last == NULL));
|
||||
// [specbot Q-NEW-2] last element must have no next (L1, O(1))
|
||||
mi_assert_internal(queue->last == NULL || queue->last->next == NULL);
|
||||
mi_assert_expensive(mi_page_queue_is_consistent(queue));
|
||||
}
|
||||
|
||||
#if MI_DEBUG >= 3
|
||||
// [specbot Q-NEW-3] Verify doubly-linked list forward/backward consistency (L2, O(n))
|
||||
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue) {
|
||||
if (queue->first == NULL) return (queue->last == NULL);
|
||||
if (queue->last == NULL) return false;
|
||||
// forward: first -> ... -> last
|
||||
const mi_page_t* p = queue->first;
|
||||
const mi_page_t* prev = NULL;
|
||||
size_t count = 0;
|
||||
while (p != NULL) {
|
||||
mi_assert_internal(p->prev == prev);
|
||||
prev = p;
|
||||
p = p->next;
|
||||
count++;
|
||||
if (count > 100000) return false; // cycle guard
|
||||
}
|
||||
mi_assert_internal(prev == queue->last);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
|
||||
static void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
|
||||
mi_assert_internal(mi_page_heap(page) == heap);
|
||||
mi_assert_internal(mi_page_queue_contains(queue, page));
|
||||
if (queue->first == page) return;
|
||||
mi_page_queue_remove(queue, page);
|
||||
mi_page_queue_push(heap, queue, page);
|
||||
mi_assert_internal(queue->first == page);
|
||||
}
|
||||
|
||||
static void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) {
|
||||
mi_assert_internal(page != NULL);
|
||||
mi_assert_expensive(mi_page_queue_contains(from, page));
|
||||
mi_assert_expensive(!mi_page_queue_contains(to, page));
|
||||
|
||||
mi_assert_internal((page->xblock_size == to->block_size && page->xblock_size == from->block_size) ||
|
||||
(page->xblock_size == to->block_size && mi_page_queue_is_full(from)) ||
|
||||
(page->xblock_size == from->block_size && mi_page_queue_is_full(to)) ||
|
||||
(page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(to)) ||
|
||||
(page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_full(to)));
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
MI_UNUSED(bsize);
|
||||
mi_assert_internal((bsize == to->block_size && bsize == from->block_size) ||
|
||||
(bsize == to->block_size && mi_page_queue_is_full(from)) ||
|
||||
(bsize == from->block_size && mi_page_queue_is_full(to)) ||
|
||||
(mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(to)) ||
|
||||
(mi_page_is_large_or_huge(page) && mi_page_queue_is_full(to)));
|
||||
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
|
||||
// delete from `from`
|
||||
if (page->prev != NULL) page->prev->next = page->next;
|
||||
if (page->next != NULL) page->next->prev = page->prev;
|
||||
if (page == from->last) from->last = page->prev;
|
||||
@@ -277,22 +339,59 @@ static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* fro
|
||||
mi_heap_queue_first_update(heap, from);
|
||||
}
|
||||
|
||||
page->prev = to->last;
|
||||
page->next = NULL;
|
||||
if (to->last != NULL) {
|
||||
mi_assert_internal(heap == mi_page_heap(to->last));
|
||||
to->last->next = page;
|
||||
to->last = page;
|
||||
// insert into `to`
|
||||
if (enqueue_at_end) {
|
||||
// enqueue at the end
|
||||
page->prev = to->last;
|
||||
page->next = NULL;
|
||||
if (to->last != NULL) {
|
||||
mi_assert_internal(heap == mi_page_heap(to->last));
|
||||
to->last->next = page;
|
||||
to->last = page;
|
||||
}
|
||||
else {
|
||||
to->first = page;
|
||||
to->last = page;
|
||||
mi_heap_queue_first_update(heap, to);
|
||||
}
|
||||
}
|
||||
else {
|
||||
to->first = page;
|
||||
to->last = page;
|
||||
mi_heap_queue_first_update(heap, to);
|
||||
if (to->first != NULL) {
|
||||
// enqueue at 2nd place
|
||||
mi_assert_internal(heap == mi_page_heap(to->first));
|
||||
mi_page_t* next = to->first->next;
|
||||
page->prev = to->first;
|
||||
page->next = next;
|
||||
to->first->next = page;
|
||||
if (next != NULL) {
|
||||
next->prev = page;
|
||||
}
|
||||
else {
|
||||
to->last = page;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// enqueue at the head (singleton list)
|
||||
page->prev = NULL;
|
||||
page->next = NULL;
|
||||
to->first = page;
|
||||
to->last = page;
|
||||
mi_heap_queue_first_update(heap, to);
|
||||
}
|
||||
}
|
||||
|
||||
mi_page_set_in_full(page, mi_page_queue_is_full(to));
|
||||
}
|
||||
|
||||
static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
|
||||
mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end */, page);
|
||||
}
|
||||
|
||||
static void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
|
||||
// note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`)
|
||||
mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end of the `to` queue? */, page);
|
||||
}
|
||||
|
||||
// Only called from `mi_heap_absorb`.
|
||||
size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) {
|
||||
mi_assert_internal(mi_heap_contains_queue(heap,pq));
|
||||
|
||||
+251
-110
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2020, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2024, 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.
|
||||
@@ -37,7 +37,7 @@ static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_sta
|
||||
}
|
||||
|
||||
static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld);
|
||||
static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld);
|
||||
static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld);
|
||||
|
||||
#if (MI_DEBUG>=3)
|
||||
static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) {
|
||||
@@ -59,7 +59,7 @@ static inline uint8_t* mi_page_area(const mi_page_t* page) {
|
||||
|
||||
static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {
|
||||
size_t psize;
|
||||
uint8_t* page_area = _mi_page_start(_mi_page_segment(page), page, &psize);
|
||||
uint8_t* page_area = _mi_segment_page_start(_mi_page_segment(page), page, &psize);
|
||||
mi_block_t* start = (mi_block_t*)page_area;
|
||||
mi_block_t* end = (mi_block_t*)(page_area + psize);
|
||||
while(p != NULL) {
|
||||
@@ -78,19 +78,38 @@ static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {
|
||||
}
|
||||
|
||||
static bool mi_page_is_valid_init(mi_page_t* page) {
|
||||
mi_assert_internal(page->xblock_size > 0);
|
||||
mi_assert_internal(mi_page_block_size(page) > 0);
|
||||
|
||||
mi_assert_internal(page->used <= page->capacity);
|
||||
mi_assert_internal(page->capacity <= page->reserved);
|
||||
|
||||
mi_segment_t* segment = _mi_page_segment(page);
|
||||
uint8_t* start = _mi_page_start(segment,page,NULL);
|
||||
mi_assert_internal(start == _mi_segment_page_start(segment,page,NULL));
|
||||
//const size_t bsize = mi_page_block_size(page);
|
||||
// [specbot P-NEW-1] block_size_shift must be consistent with block_size (L1, O(1))
|
||||
mi_assert_internal(page->block_size_shift == 0 || (mi_page_block_size(page) == ((size_t)1 << page->block_size_shift)));
|
||||
// [specbot P-NEW-2] capacity must be nonzero when blocks are in use (L1, O(1))
|
||||
mi_assert_internal(page->used == 0 || page->capacity > 0);
|
||||
// [specbot P-NEW-4] page_start must be non-null when capacity > 0 (L1, O(1))
|
||||
mi_assert_internal(page->capacity == 0 || mi_page_start(page) != NULL);
|
||||
|
||||
uint8_t* start = mi_page_start(page);
|
||||
mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL));
|
||||
mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE));
|
||||
//mi_assert_internal(start + page->capacity*page->block_size == page->top);
|
||||
|
||||
mi_assert_internal(mi_page_list_is_valid(page,page->free));
|
||||
mi_assert_internal(mi_page_list_is_valid(page,page->local_free));
|
||||
|
||||
// [specbot P-NEW-7] All free list blocks are aligned to block_size (L2, O(n))
|
||||
{
|
||||
size_t bsize = mi_page_block_size(page);
|
||||
uint8_t* pstart = mi_page_start(page);
|
||||
for (mi_block_t* b = page->free; b != NULL; b = mi_block_next(page, b)) {
|
||||
mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
|
||||
}
|
||||
for (mi_block_t* b = page->local_free; b != NULL; b = mi_block_next(page, b)) {
|
||||
mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
|
||||
}
|
||||
}
|
||||
|
||||
#if MI_DEBUG>3 // generally too expensive to check this
|
||||
if (page->free_is_zero) {
|
||||
const size_t ubsize = mi_page_usable_block_size(page);
|
||||
@@ -113,7 +132,7 @@ static bool mi_page_is_valid_init(mi_page_t* page) {
|
||||
return true;
|
||||
}
|
||||
|
||||
extern bool _mi_process_is_initialized; // has mi_process_init been called?
|
||||
extern mi_decl_hidden bool _mi_process_is_initialized; // has mi_process_init been called?
|
||||
|
||||
bool _mi_page_is_valid(mi_page_t* page) {
|
||||
mi_assert_internal(mi_page_is_valid_init(page));
|
||||
@@ -123,6 +142,9 @@ bool _mi_page_is_valid(mi_page_t* page) {
|
||||
if (mi_page_heap(page)!=NULL) {
|
||||
mi_segment_t* segment = _mi_page_segment(page);
|
||||
|
||||
// [specbot P-NEW-6] heap_tag must match owning heap's tag (L1, O(1))
|
||||
mi_assert_internal(page->heap_tag == mi_page_heap(page)->tag);
|
||||
|
||||
mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
if (segment->kind != MI_SEGMENT_HUGE)
|
||||
@@ -139,8 +161,9 @@ bool _mi_page_is_valid(mi_page_t* page) {
|
||||
#endif
|
||||
|
||||
void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {
|
||||
size_t ticks = 0;
|
||||
while (!_mi_page_try_use_delayed_free(page, delay, override_never)) {
|
||||
mi_atomic_yield();
|
||||
mi_atomic_yield_sleep(&ticks,10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +216,8 @@ static void _mi_page_thread_free_collect(mi_page_t* page)
|
||||
if (head == NULL) return;
|
||||
|
||||
// find the tail -- also to get a proper count (without data races)
|
||||
uint32_t max_count = page->capacity; // cannot collect more than capacity
|
||||
uint32_t count = 1;
|
||||
size_t max_count = page->capacity; // cannot collect more than capacity
|
||||
size_t count = 1;
|
||||
mi_block_t* tail = head;
|
||||
mi_block_t* next;
|
||||
while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) {
|
||||
@@ -202,8 +225,13 @@ static void _mi_page_thread_free_collect(mi_page_t* page)
|
||||
tail = next;
|
||||
}
|
||||
// if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free)
|
||||
if (count > max_count) {
|
||||
_mi_error_message(EFAULT, "corrupted thread-free list\n");
|
||||
if mi_unlikely(count > max_count) {
|
||||
_mi_error_message(EFAULT, "corrupted thread-free list (possibly due to a cross-thread double free)\n");
|
||||
return; // the thread-free items cannot be freed
|
||||
}
|
||||
// if `count > page->used` there was another kind memory corruption (either in the page meta-data or in the linked list)
|
||||
else if mi_unlikely(count > page->used) {
|
||||
_mi_error_message(EFAULT, "corrupted meta-data in thread-free list\n");
|
||||
return; // the thread-free items cannot be freed
|
||||
}
|
||||
|
||||
@@ -212,10 +240,12 @@ static void _mi_page_thread_free_collect(mi_page_t* page)
|
||||
page->local_free = head;
|
||||
|
||||
// update counts now
|
||||
page->used -= count;
|
||||
mi_assert_internal(count <= UINT16_MAX);
|
||||
mi_assert_internal(page->used >= (uint16_t)count);
|
||||
page->used = page->used - (uint16_t)count;
|
||||
|
||||
if (page->used == 0) {
|
||||
// The page may have had a QSBR goal set from a previous point when it
|
||||
// CPython: the page may have had a QSBR goal set from a previous point when it
|
||||
// was all-free. That goal is no longer valid because the page was
|
||||
// allocated from and then freed again by other threads.
|
||||
_PyMem_mi_page_clear_qsbr(page);
|
||||
@@ -285,19 +315,22 @@ static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size
|
||||
mi_assert_internal(mi_heap_contains_queue(heap, pq));
|
||||
mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size);
|
||||
#endif
|
||||
mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments, &heap->tld->os);
|
||||
mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments);
|
||||
if (page == NULL) {
|
||||
// this may be out-of-memory, or an abandoned page was reclaimed (and in our queue)
|
||||
return NULL;
|
||||
}
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
|
||||
#endif
|
||||
mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
|
||||
mi_assert_internal(pq!=NULL || page->xblock_size != 0);
|
||||
mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size);
|
||||
// a fresh page was found, initialize it
|
||||
const size_t full_block_size = ((pq == NULL || mi_page_queue_is_huge(pq)) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc
|
||||
const size_t full_block_size = (pq == NULL || mi_page_is_huge(page) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc
|
||||
mi_assert_internal(full_block_size >= block_size);
|
||||
mi_page_init(heap, page, full_block_size, heap->tld);
|
||||
mi_heap_stat_increase(heap, pages, 1);
|
||||
_mi_stat_increase(&heap->tld->stats.pages, 1);
|
||||
_mi_stat_increase(&heap->tld->stats.page_bins[_mi_page_stats_bin(page)], 1);
|
||||
if (pq != NULL) { mi_page_queue_push(heap, pq, page); }
|
||||
mi_assert_expensive(_mi_page_is_valid(page));
|
||||
return page;
|
||||
@@ -318,8 +351,9 @@ static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) {
|
||||
(put there by other threads if they deallocated in a full page)
|
||||
----------------------------------------------------------- */
|
||||
void _mi_heap_delayed_free_all(mi_heap_t* heap) {
|
||||
size_t ticks = 0;
|
||||
while (!_mi_heap_delayed_free_partial(heap)) {
|
||||
mi_atomic_yield();
|
||||
mi_atomic_yield_sleep(&ticks,10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +399,7 @@ void _mi_page_unfull(mi_page_t* page) {
|
||||
mi_page_set_in_full(page, false); // to get the right queue
|
||||
mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
|
||||
mi_page_set_in_full(page, true);
|
||||
mi_page_queue_enqueue_from(pq, pqfull, page);
|
||||
mi_page_queue_enqueue_from_full(pq, pqfull, page);
|
||||
}
|
||||
|
||||
static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {
|
||||
@@ -393,7 +427,7 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
if (page->qsbr_node.next != NULL) {
|
||||
// remove from QSBR queue, but keep the goal
|
||||
// CPython: remove from the QSBR queue, but keep the goal
|
||||
llist_remove(&page->qsbr_node);
|
||||
}
|
||||
#endif
|
||||
@@ -418,6 +452,32 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
|
||||
_mi_segment_page_abandon(page,segments_tld);
|
||||
}
|
||||
|
||||
// force abandon a page
|
||||
void _mi_page_force_abandon(mi_page_t* page) {
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
// mark page as not using delayed free
|
||||
_mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);
|
||||
|
||||
// ensure this page is no longer in the heap delayed free list
|
||||
_mi_heap_delayed_free_all(heap);
|
||||
// We can still access the page meta-info even if it is freed as we ensure
|
||||
// in `mi_segment_force_abandon` that the segment is not freed (yet)
|
||||
if (page->capacity == 0) return; // it may have been freed now
|
||||
|
||||
// and now unlink it from the page queue and abandon (or free)
|
||||
mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
|
||||
if (mi_page_all_free(page)) {
|
||||
// CPython: keep the page if it is still waiting for QSBR.
|
||||
if (!_PyMem_mi_page_maybe_free(page, pq, false)) {
|
||||
pq = mi_heap_page_queue_of(heap, page);
|
||||
_mi_page_abandon(page, pq);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_mi_page_abandon(page, pq);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Free a page with no more free blocks
|
||||
void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
|
||||
@@ -447,8 +507,6 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
|
||||
_mi_segment_page_free(page, force, segments_tld);
|
||||
}
|
||||
|
||||
// Retire parameters
|
||||
#define MI_MAX_RETIRE_SIZE (MI_MEDIUM_OBJ_SIZE_MAX)
|
||||
#define MI_RETIRE_CYCLES (16)
|
||||
|
||||
// Retire a page with no more used blocks
|
||||
@@ -464,7 +522,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
|
||||
|
||||
mi_page_set_has_aligned(page, false);
|
||||
|
||||
// any previous QSBR goals are no longer valid because we reused the page
|
||||
// CPython: any previous QSBR goals are no longer valid because we reused the page
|
||||
_PyMem_mi_page_clear_qsbr(page);
|
||||
|
||||
// don't retire too often..
|
||||
@@ -474,10 +532,12 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
|
||||
// how to check this efficiently though...
|
||||
// for now, we don't retire if it is the only page left of this size class.
|
||||
mi_page_queue_t* pq = mi_page_queue_of(page);
|
||||
if mi_likely(page->xblock_size <= MI_MAX_RETIRE_SIZE && !mi_page_queue_is_special(pq)) { // not too large && not full or huge queue?
|
||||
#if MI_RETIRE_CYCLES > 0
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue?
|
||||
if (pq->last==page && pq->first==page) { // the only page in the queue?
|
||||
mi_stat_counter_increase(_mi_stats_main.page_no_retire,1);
|
||||
page->retire_expire = 1 + (page->xblock_size <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
|
||||
mi_stat_counter_increase(_mi_stats_main.pages_retire,1);
|
||||
page->retire_expire = (bsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
|
||||
mi_heap_t* heap = mi_page_heap(page);
|
||||
mi_assert_internal(pq >= heap->pages);
|
||||
const size_t index = pq - heap->pages;
|
||||
@@ -488,6 +548,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
|
||||
return; // don't free after all
|
||||
}
|
||||
}
|
||||
#endif
|
||||
_PyMem_mi_page_maybe_free(page, pq, false);
|
||||
}
|
||||
|
||||
@@ -536,13 +597,13 @@ void _mi_heap_collect_retired(mi_heap_t* heap, bool force) {
|
||||
|
||||
static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) {
|
||||
MI_UNUSED(stats);
|
||||
#if (MI_SECURE<=2)
|
||||
#if (MI_SECURE < 2)
|
||||
mi_assert_internal(page->free == NULL);
|
||||
mi_assert_internal(page->local_free == NULL);
|
||||
#endif
|
||||
mi_assert_internal(page->capacity + extend <= page->reserved);
|
||||
mi_assert_internal(bsize == mi_page_block_size(page));
|
||||
void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL);
|
||||
void* const page_area = mi_page_start(page);
|
||||
|
||||
// initialize a randomized free list
|
||||
// set up `slice_count` slices to alternate between
|
||||
@@ -594,13 +655,13 @@ static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* co
|
||||
static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats)
|
||||
{
|
||||
MI_UNUSED(stats);
|
||||
#if (MI_SECURE <= 2)
|
||||
#if (MI_SECURE < 2)
|
||||
mi_assert_internal(page->free == NULL);
|
||||
mi_assert_internal(page->local_free == NULL);
|
||||
#endif
|
||||
mi_assert_internal(page->capacity + extend <= page->reserved);
|
||||
mi_assert_internal(bsize == mi_page_block_size(page));
|
||||
void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL );
|
||||
void* const page_area = mi_page_start(page);
|
||||
|
||||
mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity);
|
||||
|
||||
@@ -622,8 +683,8 @@ static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, co
|
||||
----------------------------------------------------------- */
|
||||
|
||||
#define MI_MAX_EXTEND_SIZE (4*1024) // heuristic, one OS page seems to work well.
|
||||
#if (MI_SECURE>0)
|
||||
#define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many
|
||||
#if (MI_SECURE>=2)
|
||||
#define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many blocks
|
||||
#else
|
||||
#define MI_MIN_EXTEND (4)
|
||||
#endif
|
||||
@@ -633,26 +694,23 @@ static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, co
|
||||
// Note: we also experimented with "bump" allocation on the first
|
||||
// allocations but this did not speed up any benchmark (due to an
|
||||
// extra test in malloc? or cache effects?)
|
||||
static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {
|
||||
MI_UNUSED(tld);
|
||||
static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {
|
||||
mi_assert_expensive(mi_page_is_valid_init(page));
|
||||
#if (MI_SECURE<=2)
|
||||
#if (MI_SECURE < 2)
|
||||
mi_assert(page->free == NULL);
|
||||
mi_assert(page->local_free == NULL);
|
||||
if (page->free != NULL) return;
|
||||
if (page->free != NULL) return true;
|
||||
#endif
|
||||
if (page->capacity >= page->reserved) return;
|
||||
if (page->capacity >= page->reserved) return true;
|
||||
|
||||
size_t page_size;
|
||||
_mi_page_start(_mi_page_segment(page), page, &page_size);
|
||||
mi_stat_counter_increase(tld->stats.pages_extended, 1);
|
||||
|
||||
// calculate the extend count
|
||||
const size_t bsize = (page->xblock_size < MI_HUGE_BLOCK_SIZE ? page->xblock_size : page_size);
|
||||
const size_t bsize = mi_page_block_size(page);
|
||||
size_t extend = page->reserved - page->capacity;
|
||||
mi_assert_internal(extend > 0);
|
||||
|
||||
size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/(uint32_t)bsize);
|
||||
size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/bsize);
|
||||
if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; }
|
||||
mi_assert_internal(max_extend > 0);
|
||||
|
||||
@@ -666,7 +724,7 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld)
|
||||
mi_assert_internal(extend < (1UL<<16));
|
||||
|
||||
// and append the extend the free list
|
||||
if (extend < MI_MIN_SLICES || MI_SECURE==0) { //!mi_option_is_enabled(mi_option_secure)) {
|
||||
if (extend < MI_MIN_SLICES || MI_SECURE<2) { //!mi_option_is_enabled(mi_option_secure)) {
|
||||
mi_page_free_list_extend(page, bsize, extend, &tld->stats );
|
||||
}
|
||||
else {
|
||||
@@ -674,8 +732,9 @@ static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld)
|
||||
}
|
||||
// enable the new free list
|
||||
page->capacity += (uint16_t)extend;
|
||||
mi_stat_increase(tld->stats.page_committed, extend * bsize);
|
||||
_mi_stat_increase(&tld->stats.page_committed, extend * bsize);
|
||||
mi_assert_expensive(mi_page_is_valid_init(page));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize a fresh page
|
||||
@@ -686,14 +745,12 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
|
||||
mi_assert_internal(block_size > 0);
|
||||
// set fields
|
||||
mi_page_set_heap(page, heap);
|
||||
page->tag = heap->tag;
|
||||
page->use_qsbr = heap->page_use_qsbr;
|
||||
page->debug_offset = heap->debug_offset;
|
||||
page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start
|
||||
page->use_qsbr = heap->page_use_qsbr; // CPython
|
||||
page->debug_offset = heap->debug_offset; // CPython
|
||||
page->block_size = block_size;
|
||||
size_t page_size;
|
||||
const void* page_start = _mi_segment_page_start(segment, page, &page_size);
|
||||
MI_UNUSED(page_start);
|
||||
mi_track_mem_noaccess(page_start,page_size);
|
||||
page->page_start = _mi_segment_page_start(segment, page, &page_size);
|
||||
mi_track_mem_noaccess(page->page_start,page_size);
|
||||
mi_assert_internal(mi_page_block_size(page) <= page_size);
|
||||
mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE);
|
||||
mi_assert_internal(page_size / block_size < (1L<<16));
|
||||
@@ -706,12 +763,18 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
|
||||
page->free_is_zero = page->is_zero_init;
|
||||
#if MI_DEBUG>2
|
||||
if (page->is_zero_init) {
|
||||
mi_track_mem_defined(page_start, page_size);
|
||||
mi_assert_expensive(mi_mem_is_zero(page_start, page_size));
|
||||
mi_track_mem_defined(page->page_start, page_size);
|
||||
mi_assert_expensive(mi_mem_is_zero(page->page_start, page_size));
|
||||
}
|
||||
#endif
|
||||
|
||||
mi_assert_internal(page->is_committed);
|
||||
if (block_size > 0 && _mi_is_power_of_two(block_size)) {
|
||||
page->block_size_shift = (uint8_t)(mi_ctz((uintptr_t)block_size));
|
||||
}
|
||||
else {
|
||||
page->block_size_shift = 0;
|
||||
}
|
||||
|
||||
mi_assert_internal(page->capacity == 0);
|
||||
mi_assert_internal(page->free == NULL);
|
||||
mi_assert_internal(page->used == 0);
|
||||
@@ -728,11 +791,14 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
|
||||
mi_assert_internal(page->keys[0] != 0);
|
||||
mi_assert_internal(page->keys[1] != 0);
|
||||
#endif
|
||||
mi_assert_internal(page->block_size_shift == 0 || (block_size == ((size_t)1 << page->block_size_shift)));
|
||||
mi_assert_expensive(mi_page_is_valid_init(page));
|
||||
|
||||
// initialize an initial free list
|
||||
mi_page_extend_free(heap,page,tld);
|
||||
mi_assert(mi_page_immediate_available(page));
|
||||
if (mi_page_extend_free(heap,page,tld)) {
|
||||
mi_assert(mi_page_immediate_available(page));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -740,6 +806,17 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
|
||||
Find pages with free blocks
|
||||
-------------------------------------------------------------*/
|
||||
|
||||
// search for a best next page to use for at most N pages (often cut short if immediate blocks are available)
|
||||
#define MI_MAX_CANDIDATE_SEARCH (4)
|
||||
|
||||
// is the page not yet used up to its reserved space?
|
||||
static bool mi_page_is_expandable(const mi_page_t* page) {
|
||||
mi_assert_internal(page != NULL);
|
||||
mi_assert_internal(page->capacity <= page->reserved);
|
||||
return (page->capacity < page->reserved);
|
||||
}
|
||||
|
||||
|
||||
// Find a page with free blocks of `page->block_size`.
|
||||
static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try)
|
||||
{
|
||||
@@ -747,41 +824,85 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
|
||||
#if MI_STAT
|
||||
size_t count = 0;
|
||||
#endif
|
||||
size_t candidate_count = 0; // we reset this on the first candidate to limit the search
|
||||
mi_page_t* page_candidate = NULL; // a page with free space
|
||||
mi_page_t* page = pq->first;
|
||||
|
||||
while (page != NULL)
|
||||
{
|
||||
mi_page_t* next = page->next; // remember next
|
||||
#if MI_STAT
|
||||
count++;
|
||||
#endif
|
||||
candidate_count++;
|
||||
|
||||
// 0. collect freed blocks by us and other threads
|
||||
// collect freed blocks by us and other threads
|
||||
_mi_page_free_collect(page, false);
|
||||
|
||||
// 1. if the page contains free blocks, we are done
|
||||
if (mi_page_immediate_available(page)) {
|
||||
#if MI_MAX_CANDIDATE_SEARCH > 1
|
||||
// search up to N pages for a best candidate
|
||||
|
||||
// is the local free list non-empty?
|
||||
const bool immediate_available = mi_page_immediate_available(page);
|
||||
|
||||
// if the page is completely full, move it to the `mi_pages_full`
|
||||
// queue so we don't visit long-lived pages too often.
|
||||
if (!immediate_available && !mi_page_is_expandable(page)) {
|
||||
mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
|
||||
mi_page_to_full(page, pq);
|
||||
}
|
||||
else {
|
||||
// the page has free space, make it a candidate
|
||||
// we prefer non-expandable pages with high usage as candidates (to reduce commit, and increase chances of free-ing up pages)
|
||||
if (page_candidate == NULL) {
|
||||
page_candidate = page;
|
||||
candidate_count = 0;
|
||||
}
|
||||
// prefer to reuse fuller pages (in the hope the less used page gets freed)
|
||||
else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page) && !mi_page_is_expandable(page)) {
|
||||
page_candidate = page;
|
||||
}
|
||||
// if we find a non-expandable candidate, or searched for N pages, return with the best candidate
|
||||
if (immediate_available || candidate_count > MI_MAX_CANDIDATE_SEARCH) {
|
||||
mi_assert_internal(page_candidate!=NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// first-fit algorithm
|
||||
// If the page contains free blocks, we are done
|
||||
if (mi_page_immediate_available(page) || mi_page_is_expandable(page)) {
|
||||
break; // pick this one
|
||||
}
|
||||
|
||||
// 2. Try to extend
|
||||
if (page->capacity < page->reserved) {
|
||||
mi_page_extend_free(heap, page, heap->tld);
|
||||
mi_assert_internal(mi_page_immediate_available(page));
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. If the page is completely full, move it to the `mi_pages_full`
|
||||
// If the page is completely full, move it to the `mi_pages_full`
|
||||
// queue so we don't visit long-lived pages too often.
|
||||
mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
|
||||
mi_page_to_full(page, pq);
|
||||
#endif
|
||||
|
||||
page = next;
|
||||
} // for each page
|
||||
|
||||
mi_heap_stat_counter_increase(heap, searches, count);
|
||||
mi_heap_stat_counter_increase(heap, page_searches, count);
|
||||
mi_heap_stat_counter_increase(heap, page_searches_count, 1);
|
||||
|
||||
// set the page to the best candidate
|
||||
if (page_candidate != NULL) {
|
||||
page = page_candidate;
|
||||
}
|
||||
if (page != NULL) {
|
||||
if (!mi_page_immediate_available(page)) {
|
||||
mi_assert_internal(mi_page_is_expandable(page));
|
||||
if (!mi_page_extend_free(heap, page, heap->tld)) {
|
||||
page = NULL; // failed to extend
|
||||
}
|
||||
}
|
||||
mi_assert_internal(page == NULL || mi_page_immediate_available(page));
|
||||
}
|
||||
|
||||
if (page == NULL) {
|
||||
_PyMem_mi_heap_collect_qsbr(heap); // some pages might be safe to free now
|
||||
_PyMem_mi_heap_collect_qsbr(heap); // CPython: some pages might be safe to free now
|
||||
_mi_heap_collect_retired(heap, false); // perhaps make a page available?
|
||||
page = mi_page_fresh(heap, pq);
|
||||
if (page == NULL && first_try) {
|
||||
@@ -790,11 +911,15 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
|
||||
}
|
||||
}
|
||||
else {
|
||||
mi_assert(pq->first == page);
|
||||
// move the page to the front of the queue
|
||||
mi_page_queue_move_to_front(heap, pq, page);
|
||||
page->retire_expire = 0;
|
||||
_PyMem_mi_page_clear_qsbr(page);
|
||||
// _mi_heap_collect_retired(heap, false); // update retire counts; note: increases rss on MemoryLoad bench so don't do this
|
||||
}
|
||||
mi_assert_internal(page == NULL || mi_page_immediate_available(page));
|
||||
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -802,10 +927,12 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
|
||||
|
||||
// Find a page with free blocks of `size`.
|
||||
static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
|
||||
mi_page_queue_t* pq = mi_page_queue(heap,size);
|
||||
mi_page_queue_t* pq = mi_page_queue(heap, size);
|
||||
|
||||
// check the first page: we even do this with candidate search or otherwise we re-search every time
|
||||
mi_page_t* page = pq->first;
|
||||
if (page != NULL) {
|
||||
#if (MI_SECURE>=3) // in secure mode, we extend half the time to increase randomness
|
||||
#if (MI_SECURE>=2) // in secure mode, we extend half the time to increase randomness
|
||||
if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) {
|
||||
mi_page_extend_free(heap, page, heap->tld);
|
||||
mi_assert_internal(mi_page_immediate_available(page));
|
||||
@@ -822,6 +949,7 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
|
||||
return page; // fast path
|
||||
}
|
||||
}
|
||||
|
||||
return mi_page_queue_find_free_ex(heap, pq, true);
|
||||
}
|
||||
|
||||
@@ -833,21 +961,24 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
|
||||
a certain number of allocations.
|
||||
----------------------------------------------------------- */
|
||||
|
||||
static mi_deferred_free_fun* volatile deferred_free = NULL;
|
||||
static _Atomic(void*) deferred_arg; // = NULL
|
||||
// The program should only install a single deferred free handler before doing allocation.
|
||||
static _Atomic(void*) deferred_free; // is `mi_deferred_free_fun*` (but some platforms don't support atomic function pointers)
|
||||
static _Atomic(void*) deferred_arg;
|
||||
|
||||
void _mi_deferred_free(mi_heap_t* heap, bool force) {
|
||||
heap->tld->heartbeat++;
|
||||
if (deferred_free != NULL && !heap->tld->recurse) {
|
||||
mi_deferred_free_fun* const fun = (mi_deferred_free_fun*)mi_atomic_load_ptr_acquire(void,&deferred_free);
|
||||
if (fun != NULL && !heap->tld->recurse) {
|
||||
heap->tld->recurse = true;
|
||||
deferred_free(force, heap->tld->heartbeat, mi_atomic_load_ptr_relaxed(void,&deferred_arg));
|
||||
void* const arg = mi_atomic_load_ptr_acquire(void,&deferred_arg);
|
||||
fun(force, heap->tld->heartbeat, arg);
|
||||
heap->tld->recurse = false;
|
||||
}
|
||||
}
|
||||
|
||||
void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept {
|
||||
deferred_free = fn;
|
||||
mi_atomic_store_ptr_release(void,&deferred_arg, arg);
|
||||
mi_atomic_store_ptr_release(void,&deferred_free, (void*)fn);
|
||||
}
|
||||
|
||||
|
||||
@@ -856,11 +987,9 @@ void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noex
|
||||
----------------------------------------------------------- */
|
||||
|
||||
// Large and huge page allocation.
|
||||
// Huge pages are allocated directly without being in a queue.
|
||||
// Because huge pages contain just one block, and the segment contains
|
||||
// just that page, we always treat them as abandoned and any thread
|
||||
// that frees the block can free the whole page and segment directly.
|
||||
// Huge pages are also use if the requested alignment is very large (> MI_ALIGNMENT_MAX).
|
||||
// Huge pages contain just one block, and the segment contains just that page (as `MI_SEGMENT_HUGE`).
|
||||
// Huge pages are also use if the requested alignment is very large (> MI_BLOCK_ALIGNMENT_MAX)
|
||||
// so their size is not always `> MI_LARGE_OBJ_SIZE_MAX`.
|
||||
static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) {
|
||||
size_t block_size = _mi_os_good_alloc_size(size);
|
||||
mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0);
|
||||
@@ -868,7 +997,7 @@ static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size));
|
||||
#else
|
||||
mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_HUGE_BLOCK_SIZE : block_size); // not block_size as that can be low if the page_alignment > 0
|
||||
mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_LARGE_OBJ_SIZE_MAX+1 : block_size);
|
||||
mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq));
|
||||
#endif
|
||||
mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment);
|
||||
@@ -876,6 +1005,7 @@ static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t
|
||||
mi_assert_internal(mi_page_immediate_available(page));
|
||||
|
||||
if (is_huge) {
|
||||
mi_assert_internal(mi_page_is_huge(page));
|
||||
mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE);
|
||||
mi_assert_internal(_mi_page_segment(page)->used==1);
|
||||
#if MI_HUGE_PAGE_ABANDON
|
||||
@@ -884,17 +1014,18 @@ static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
|
||||
mi_assert_internal(!mi_page_is_huge(page));
|
||||
}
|
||||
|
||||
const size_t bsize = mi_page_usable_block_size(page); // note: not `mi_page_block_size` to account for padding
|
||||
if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
mi_heap_stat_increase(heap, large, bsize);
|
||||
mi_heap_stat_counter_increase(heap, large_count, 1);
|
||||
/*if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
|
||||
mi_heap_stat_increase(heap, malloc_large, bsize);
|
||||
mi_heap_stat_counter_increase(heap, malloc_large_count, 1);
|
||||
}
|
||||
else {
|
||||
mi_heap_stat_increase(heap, huge, bsize);
|
||||
mi_heap_stat_counter_increase(heap, huge_count, 1);
|
||||
else */
|
||||
{
|
||||
_mi_stat_increase(&heap->tld->stats.malloc_huge, bsize);
|
||||
_mi_stat_counter_increase(&heap->tld->stats.malloc_huge_count, 1);
|
||||
}
|
||||
}
|
||||
return page;
|
||||
@@ -907,12 +1038,12 @@ static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignme
|
||||
// huge allocation?
|
||||
const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size`
|
||||
if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) {
|
||||
if mi_unlikely(req_size > PTRDIFF_MAX) { // we don't allocate more than PTRDIFF_MAX (see <https://sourceware.org/ml/libc-announce/2019/msg00001.html>)
|
||||
if mi_unlikely(req_size > MI_MAX_ALLOC_SIZE) {
|
||||
_mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size);
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
_PyMem_mi_heap_collect_qsbr(heap);
|
||||
_PyMem_mi_heap_collect_qsbr(heap); // CPython
|
||||
return mi_large_huge_page_alloc(heap,size,huge_alignment);
|
||||
}
|
||||
}
|
||||
@@ -927,9 +1058,9 @@ static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignme
|
||||
|
||||
// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed.
|
||||
// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.
|
||||
// The `huge_alignment` is normally 0 but is set to a multiple of MI_SEGMENT_SIZE for
|
||||
// very large requested alignments in which case we use a huge segment.
|
||||
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept
|
||||
// The `huge_alignment` is normally 0 but is set to a multiple of MI_SLICE_SIZE for
|
||||
// very large requested alignments in which case we use a huge singleton page.
|
||||
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, mi_page_t** ppage) mi_attr_noexcept
|
||||
{
|
||||
mi_assert_internal(heap != NULL);
|
||||
|
||||
@@ -940,11 +1071,23 @@ void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_al
|
||||
}
|
||||
mi_assert_internal(mi_heap_is_initialized(heap));
|
||||
|
||||
// call potential deferred free routines
|
||||
_mi_deferred_free(heap, false);
|
||||
// do administrative tasks every N generic mallocs
|
||||
if mi_unlikely(++heap->generic_count >= 100) {
|
||||
heap->generic_collect_count += heap->generic_count;
|
||||
heap->generic_count = 0;
|
||||
// call potential deferred free routines
|
||||
_mi_deferred_free(heap, false);
|
||||
|
||||
// free delayed frees from other threads (but skip contended ones)
|
||||
_mi_heap_delayed_free_partial(heap);
|
||||
// free delayed frees from other threads (but skip contended ones)
|
||||
_mi_heap_delayed_free_partial(heap);
|
||||
|
||||
// collect every once in a while (10000 by default)
|
||||
const long generic_collect = mi_option_get_clamp(mi_option_generic_collect, 1, 1000000L);
|
||||
if (heap->generic_collect_count >= generic_collect) {
|
||||
heap->generic_collect_count = 0;
|
||||
mi_heap_collect(heap, false /* force? */);
|
||||
}
|
||||
}
|
||||
|
||||
// find (or allocate) a page of the right size
|
||||
mi_page_t* page = mi_find_page(heap, size, huge_alignment);
|
||||
@@ -963,14 +1106,12 @@ void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_al
|
||||
mi_assert_internal(mi_page_block_size(page) >= size);
|
||||
|
||||
// and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc)
|
||||
if mi_unlikely(zero && page->xblock_size == 0) {
|
||||
// note: we cannot call _mi_page_malloc with zeroing for huge blocks; we zero it afterwards in that case.
|
||||
void* p = _mi_page_malloc(heap, page, size, false);
|
||||
mi_assert_internal(p != NULL);
|
||||
_mi_memzero_aligned(p, mi_page_usable_block_size(page));
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
return _mi_page_malloc(heap, page, size, zero);
|
||||
void* const p = _mi_page_malloc_zero(heap, page, size, zero, ppage);
|
||||
mi_assert_internal(p != NULL);
|
||||
|
||||
// move singleton pages to the full queue
|
||||
if (page->reserved == page->used) {
|
||||
mi_page_to_full(page, mi_page_queue_of(page));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen, Alon Zakai
|
||||
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.
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
// This file is included in `src/prim/prim.c`
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h"
|
||||
|
||||
#include <unistd.h> // getentropy
|
||||
|
||||
// Design
|
||||
// ======
|
||||
//
|
||||
// mimalloc is built on top of emmalloc. emmalloc is a minimal allocator on top
|
||||
// of sbrk. The reason for having three layers here is that we want mimalloc to
|
||||
// be able to allocate and release system memory properly, the same way it would
|
||||
// when using VirtualAlloc on Windows or mmap on POSIX, and sbrk is too limited.
|
||||
// Specifically, sbrk can only go up and down, and not "skip" over regions, and
|
||||
// so we end up either never freeing memory to the system, or we can get stuck
|
||||
// with holes.
|
||||
//
|
||||
// Atm wasm generally does *not* free memory back the system: once grown, we do
|
||||
// not shrink back down (https://github.com/WebAssembly/design/issues/1397).
|
||||
// However, that is expected to improve
|
||||
// (https://github.com/WebAssembly/memory-control/blob/main/proposals/memory-control/Overview.md)
|
||||
// and so we do not want to bake those limitations in here.
|
||||
//
|
||||
// Even without that issue, we want our system allocator to handle holes, that
|
||||
// is, it should merge freed regions and allow allocating new content there of
|
||||
// the full size, etc., so that we do not waste space. That means that the
|
||||
// system allocator really does need to handle the general problem of allocating
|
||||
// and freeing variable-sized chunks of memory in a random order, like malloc/
|
||||
// free do. And so it makes sense to layer mimalloc on top of such an
|
||||
// implementation.
|
||||
//
|
||||
// emmalloc makes sense for the lower level because it is small and simple while
|
||||
// still fully handling merging of holes etc. It is not the most efficient
|
||||
// allocator, but our assumption is that mimalloc needs to be fast while the
|
||||
// system allocator underneath it is called much less frequently.
|
||||
//
|
||||
|
||||
//---------------------------------------------
|
||||
// init
|
||||
//---------------------------------------------
|
||||
|
||||
void _mi_prim_mem_init( mi_os_mem_config_t* config) {
|
||||
config->page_size = 64*MI_KiB; // WebAssembly has a fixed page size: 64KiB
|
||||
config->alloc_granularity = 16;
|
||||
config->has_overcommit = false;
|
||||
config->has_partial_free = false;
|
||||
config->has_virtual_reserve = false;
|
||||
}
|
||||
|
||||
extern void emmalloc_free(void*);
|
||||
|
||||
int _mi_prim_free(void* addr, size_t size) {
|
||||
emmalloc_free(addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------
|
||||
// Allocation
|
||||
//---------------------------------------------
|
||||
|
||||
extern void* emmalloc_memalign(size_t alignment, size_t size);
|
||||
|
||||
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
|
||||
int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
|
||||
MI_UNUSED(try_alignment); MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr);
|
||||
*is_large = false;
|
||||
// TODO: Track the highest address ever seen; first uses of it are zeroes.
|
||||
// That assumes no one else uses sbrk but us (they could go up,
|
||||
// scribble, and then down), but we could assert on that perhaps.
|
||||
*is_zero = false;
|
||||
// emmalloc has a minimum alignment size.
|
||||
#define MIN_EMMALLOC_ALIGN 8
|
||||
if (try_alignment < MIN_EMMALLOC_ALIGN) {
|
||||
try_alignment = MIN_EMMALLOC_ALIGN;
|
||||
}
|
||||
void* p = emmalloc_memalign(try_alignment, size);
|
||||
*addr = p;
|
||||
if (p == 0) {
|
||||
return ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------
|
||||
// Commit/Reset
|
||||
//---------------------------------------------
|
||||
|
||||
int _mi_prim_commit(void* addr, size_t size, bool* is_zero) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size);
|
||||
// See TODO above.
|
||||
*is_zero = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size);
|
||||
*needs_recommit = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_reset(void* addr, size_t size) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_reuse(void* addr, size_t size) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_protect(void* addr, size_t size, bool protect) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------
|
||||
// Huge pages and NUMA nodes
|
||||
//---------------------------------------------
|
||||
|
||||
int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) {
|
||||
MI_UNUSED(hint_addr); MI_UNUSED(size); MI_UNUSED(numa_node);
|
||||
*is_zero = true;
|
||||
*addr = NULL;
|
||||
return ENOSYS;
|
||||
}
|
||||
|
||||
size_t _mi_prim_numa_node(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t _mi_prim_numa_node_count(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Clock
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
// todo: use a monotonic clock instead
|
||||
return emscripten_date_now();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Process info
|
||||
//----------------------------------------------------------------
|
||||
|
||||
void _mi_prim_process_info(mi_process_info_t* pinfo)
|
||||
{
|
||||
// use defaults
|
||||
MI_UNUSED(pinfo);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Output
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#include <emscripten/console.h>
|
||||
|
||||
void _mi_prim_out_stderr( const char* msg) {
|
||||
emscripten_console_error(msg);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Environment
|
||||
//----------------------------------------------------------------
|
||||
|
||||
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
// For code size reasons, do not support environ customization for now.
|
||||
MI_UNUSED(name);
|
||||
MI_UNUSED(result);
|
||||
MI_UNUSED(result_size);
|
||||
return 0; // not found
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Random
|
||||
//----------------------------------------------------------------
|
||||
|
||||
bool _mi_prim_random_buf(void* buf, size_t buf_len) {
|
||||
int err = getentropy(buf, buf_len);
|
||||
return !err;
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Thread init/done
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#if defined(MI_USE_PTHREADS)
|
||||
|
||||
// use pthread local storage keys to detect thread ending
|
||||
// (and used with MI_TLS_PTHREADS for the default heap)
|
||||
pthread_key_t _mi_heap_default_key = (pthread_key_t)(-1);
|
||||
|
||||
static void mi_pthread_done(void* value) {
|
||||
if (value!=NULL) {
|
||||
_mi_thread_done((mi_heap_t*)value);
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_prim_thread_init_auto_done(void) {
|
||||
mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));
|
||||
pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
|
||||
}
|
||||
|
||||
void _mi_prim_thread_done_auto_done(void) {
|
||||
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // do not leak the key, see issue #809
|
||||
pthread_key_delete(_mi_heap_default_key);
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {
|
||||
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // can happen during recursive invocation on freeBSD
|
||||
pthread_setspecific(_mi_heap_default_key, heap);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void _mi_prim_thread_init_auto_done(void) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void _mi_prim_thread_done_auto_done(void) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {
|
||||
MI_UNUSED(heap);
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -41,10 +41,19 @@ extern malloc_zone_t* malloc_default_purgeable_zone(void) __attribute__((weak_im
|
||||
malloc zone members
|
||||
------------------------------------------------------ */
|
||||
|
||||
static bool is_mimalloc_zone( malloc_zone_t* zone );
|
||||
|
||||
static size_t zone_size(malloc_zone_t* zone, const void* p) {
|
||||
MI_UNUSED(zone);
|
||||
if (!mi_is_in_heap_region(p)){ return 0; } // not our pointer, bail out
|
||||
return mi_usable_size(p);
|
||||
if (mi_is_in_heap_region(p)) {
|
||||
return mi_usable_size(p);
|
||||
}
|
||||
else if (!is_mimalloc_zone(zone)) { // can happen due to interpose
|
||||
return zone->size(zone,p);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void* zone_malloc(malloc_zone_t* zone, size_t size) {
|
||||
@@ -63,13 +72,24 @@ static void* zone_valloc(malloc_zone_t* zone, size_t size) {
|
||||
}
|
||||
|
||||
static void zone_free(malloc_zone_t* zone, void* p) {
|
||||
MI_UNUSED(zone);
|
||||
mi_cfree(p);
|
||||
if (mi_is_in_heap_region(p)) {
|
||||
mi_free(p);
|
||||
}
|
||||
else if (!is_mimalloc_zone(zone)) { // can happen due to interpose
|
||||
zone->free(zone,p);
|
||||
}
|
||||
}
|
||||
|
||||
static void* zone_realloc(malloc_zone_t* zone, void* p, size_t newsize) {
|
||||
MI_UNUSED(zone);
|
||||
return mi_realloc(p, newsize);
|
||||
if (p == NULL || mi_is_in_heap_region(p)) {
|
||||
return mi_realloc(p, newsize);
|
||||
}
|
||||
else if (!is_mimalloc_zone(zone)) { // can happen due to interpose
|
||||
return zone->realloc(zone,p,newsize);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void* zone_memalign(malloc_zone_t* zone, size_t alignment, size_t size) {
|
||||
@@ -78,12 +98,13 @@ static void* zone_memalign(malloc_zone_t* zone, size_t alignment, size_t size) {
|
||||
}
|
||||
|
||||
static void zone_destroy(malloc_zone_t* zone) {
|
||||
MI_UNUSED(zone);
|
||||
// todo: ignore for now?
|
||||
if (!is_mimalloc_zone(zone)) {
|
||||
zone->destroy(zone);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned zone_batch_malloc(malloc_zone_t* zone, size_t size, void** ps, unsigned count) {
|
||||
size_t i;
|
||||
unsigned i;
|
||||
for (i = 0; i < count; i++) {
|
||||
ps[i] = zone_malloc(zone, size);
|
||||
if (ps[i] == NULL) break;
|
||||
@@ -114,7 +135,6 @@ static boolean_t zone_claimed_address(malloc_zone_t* zone, void* p) {
|
||||
return mi_is_in_heap_region(p);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------
|
||||
Introspection members
|
||||
------------------------------------------------------ */
|
||||
@@ -174,6 +194,14 @@ static boolean_t intro_zone_locked(malloc_zone_t* zone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Required whenever the zone advertises version >= 9: macOS calls this from the
|
||||
// atfork_child handler (_malloc_fork_child) without a NULL check. mimalloc keeps
|
||||
// no zone-level locks that need reinitializing after fork, so a no-op is safe.
|
||||
// Leaving it NULL makes the forked child jump to address 0 and crash in fork().
|
||||
static void intro_reinit_lock(malloc_zone_t* zone) {
|
||||
MI_UNUSED(zone);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------
|
||||
At process start, override the default allocator
|
||||
@@ -199,6 +227,9 @@ static malloc_introspection_t mi_introspect = {
|
||||
.statistics = &intro_statistics,
|
||||
.zone_locked = &intro_zone_locked,
|
||||
#endif
|
||||
#if defined(MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12) && !defined(__ppc__)
|
||||
.reinit_lock = &intro_reinit_lock,
|
||||
#endif
|
||||
};
|
||||
|
||||
static malloc_zone_t mi_malloc_zone = {
|
||||
@@ -225,7 +256,9 @@ static malloc_zone_t mi_malloc_zone = {
|
||||
// switch to version 9+ on OSX 10.6 to support memalign.
|
||||
.memalign = &zone_memalign,
|
||||
.free_definite_size = &zone_free_definite_size,
|
||||
#if defined(MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
.pressure_relief = &zone_pressure_relief,
|
||||
#endif
|
||||
#if defined(MAC_OS_X_VERSION_10_14) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)
|
||||
.claimed_address = &zone_claimed_address,
|
||||
#endif
|
||||
@@ -234,6 +267,11 @@ static malloc_zone_t mi_malloc_zone = {
|
||||
#endif
|
||||
};
|
||||
|
||||
static bool is_mimalloc_zone( malloc_zone_t* zone ) {
|
||||
return (zone==NULL || zone==&mi_malloc_zone);
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -251,11 +289,8 @@ static malloc_zone_t mi_malloc_zone = {
|
||||
// `malloc_zone_calloc` etc. see <https://github.com/aosm/libmalloc/blob/master/man/malloc_zone_malloc.3>
|
||||
// ------------------------------------------------------
|
||||
|
||||
static inline malloc_zone_t* mi_get_default_zone(void)
|
||||
{
|
||||
static bool init;
|
||||
if mi_unlikely(!init) {
|
||||
init = true;
|
||||
static inline malloc_zone_t* mi_get_default_zone(void) {
|
||||
mi_atomic_do_once {
|
||||
malloc_zone_register(&mi_malloc_zone); // by calling register we avoid a zone error on free (see <http://eatmyrandom.blogspot.com/2010/03/mallocfree-interception-on-mac-os-x.html>)
|
||||
}
|
||||
return &mi_malloc_zone;
|
||||
@@ -326,7 +361,7 @@ static bool zone_check(malloc_zone_t* zone) {
|
||||
|
||||
static malloc_zone_t* zone_from_ptr(const void* p) {
|
||||
MI_UNUSED(p);
|
||||
return mi_get_default_zone();
|
||||
return (mi_is_in_heap_region(p) ? mi_get_default_zone() : NULL);
|
||||
}
|
||||
|
||||
static void zone_log(malloc_zone_t* zone, void* p) {
|
||||
@@ -416,10 +451,11 @@ static inline malloc_zone_t* mi_get_default_zone(void)
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
__attribute__((constructor(0)))
|
||||
__attribute__((constructor(101))) // highest priority
|
||||
#else
|
||||
__attribute__((constructor)) // seems not supported by g++-11 on the M1
|
||||
__attribute__((constructor)) // priority level is not supported by gcc
|
||||
#endif
|
||||
__attribute__((used))
|
||||
static void _mi_macos_override_malloc(void) {
|
||||
malloc_zone_t* purgeable_zone = NULL;
|
||||
|
||||
|
||||
@@ -18,7 +18,60 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#define MI_USE_SBRK
|
||||
#include "wasi/prim.c" // memory-grow or sbrk (Wasm)
|
||||
|
||||
// CPython: use the unix prim on Emscripten (emmalloc is not linked)
|
||||
#elif defined(__EMSCRIPTEN__) && 0
|
||||
#include "emscripten/prim.c" // emmalloc_*, + pthread support
|
||||
|
||||
#else
|
||||
#include "unix/prim.c" // mmap() (Linux, macOSX, BSD, Illumnos, Haiku, DragonFly, etc.)
|
||||
|
||||
#endif
|
||||
|
||||
// Generic process initialization
|
||||
#ifndef MI_PRIM_HAS_PROCESS_ATTACH
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// gcc,clang: use the constructor/destructor attribute
|
||||
// which for both seem to run before regular constructors/destructors
|
||||
#if defined(__clang__)
|
||||
#define mi_attr_constructor __attribute__((constructor(101)))
|
||||
#define mi_attr_destructor __attribute__((destructor(101)))
|
||||
#else
|
||||
#define mi_attr_constructor __attribute__((constructor))
|
||||
#define mi_attr_destructor __attribute__((destructor))
|
||||
#endif
|
||||
static void mi_attr_constructor mi_process_attach(void) {
|
||||
_mi_auto_process_init();
|
||||
}
|
||||
static void mi_attr_destructor mi_process_detach(void) {
|
||||
_mi_auto_process_done();
|
||||
}
|
||||
#elif defined(__cplusplus)
|
||||
// C++: use static initialization to detect process start/end
|
||||
// This is not guaranteed to be first/last but the best we can generally do?
|
||||
struct mi_init_done_t {
|
||||
mi_init_done_t() {
|
||||
_mi_auto_process_init();
|
||||
}
|
||||
~mi_init_done_t() {
|
||||
_mi_auto_process_done();
|
||||
}
|
||||
};
|
||||
static mi_init_done_t mi_init_done;
|
||||
#else
|
||||
#pragma message("define a way to call _mi_auto_process_init/done on your platform")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Generic allocator init/done callback
|
||||
#ifndef MI_PRIM_HAS_ALLOCATOR_INIT
|
||||
bool _mi_is_redirected(void) {
|
||||
return false;
|
||||
}
|
||||
bool _mi_allocator_init(const char** message) {
|
||||
if (message != NULL) { *message = NULL; }
|
||||
return true;
|
||||
}
|
||||
void _mi_allocator_done(void) {
|
||||
// nothing to do
|
||||
}
|
||||
#endif
|
||||
+315
-188
@@ -1,5 +1,5 @@
|
||||
/* ----------------------------------------------------------------------------
|
||||
Copyright (c) 2018-2023, Microsoft Research, Daan Leijen
|
||||
Copyright (c) 2018-2025, 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.
|
||||
@@ -22,26 +22,35 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h"
|
||||
|
||||
#include <sys/mman.h> // mmap
|
||||
#include <unistd.h> // sysconf
|
||||
#include <fcntl.h> // open, close, read, access
|
||||
#include <stdlib.h> // getenv, arc4random_buf
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <features.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/prctl.h> // THP disable, PR_SET_VMA
|
||||
#include <sys/sysinfo.h> // sysinfo
|
||||
#if defined(__GLIBC__) && !defined(PR_SET_VMA)
|
||||
#include <linux/prctl.h>
|
||||
#endif
|
||||
#if defined(__GLIBC__)
|
||||
#include <linux/mman.h> // linux mmap flags
|
||||
#include <linux/mman.h> // linux mmap flags
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <TargetConditionals.h>
|
||||
#if !TARGET_IOS_IPHONE && !TARGET_IOS_SIMULATOR
|
||||
#include <mach/vm_statistics.h>
|
||||
#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX // see issue #879, used to be (!TARGET_IOS_IPHONE && !TARGET_IOS_SIMULATOR)
|
||||
#include <mach/vm_statistics.h> // VM_MAKE_TAG, VM_FLAGS_SUPERPAGE_SIZE_2MB, etc.
|
||||
#endif
|
||||
#if !defined(MAC_OS_X_VERSION_10_7)
|
||||
#define MAC_OS_X_VERSION_10_7 1070
|
||||
#endif
|
||||
#include <sys/sysctl.h>
|
||||
#elif defined(__FreeBSD__) || defined(__DragonFly__)
|
||||
#include <sys/param.h>
|
||||
#if __FreeBSD_version >= 1200000
|
||||
@@ -51,44 +60,54 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#if !defined(__HAIKU__) && !defined(__APPLE__) && !defined(__CYGWIN__) && !defined(_AIX) && !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__sun) && !defined(__NetBSD__)
|
||||
#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__)
|
||||
#define MI_HAS_SYSCALL_H
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
#if !defined(MADV_DONTNEED) && defined(POSIX_MADV_DONTNEED) // QNX
|
||||
#define MADV_DONTNEED POSIX_MADV_DONTNEED
|
||||
#endif
|
||||
#if !defined(MADV_FREE) && defined(POSIX_MADV_FREE) // QNX
|
||||
#define MADV_FREE POSIX_MADV_FREE
|
||||
#endif
|
||||
|
||||
#define MI_UNIX_LARGE_PAGE_SIZE (2*MI_MiB) // TODO: can we query the OS for this?
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Use syscalls for some primitives to allow for libraries that override open/read/close etc.
|
||||
// and do allocation themselves; using syscalls prevents recursion when mimalloc is
|
||||
// still initializing (issue #713)
|
||||
// Declare inline to avoid unused function warnings.
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
#if defined(MI_HAS_SYSCALL_H) && defined(SYS_open) && defined(SYS_close) && defined(SYS_read) && defined(SYS_access)
|
||||
|
||||
static int mi_prim_open(const char* fpath, int open_flags) {
|
||||
static inline int mi_prim_open(const char* fpath, int open_flags) {
|
||||
return syscall(SYS_open,fpath,open_flags,0);
|
||||
}
|
||||
static ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
|
||||
static inline ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
|
||||
return syscall(SYS_read,fd,buf,bufsize);
|
||||
}
|
||||
static int mi_prim_close(int fd) {
|
||||
static inline int mi_prim_close(int fd) {
|
||||
return syscall(SYS_close,fd);
|
||||
}
|
||||
static int mi_prim_access(const char *fpath, int mode) {
|
||||
static inline int mi_prim_access(const char *fpath, int mode) {
|
||||
return syscall(SYS_access,fpath,mode);
|
||||
}
|
||||
|
||||
#elif !defined(__APPLE__) && !defined(_AIX) && !defined(__OpenBSD__) && !defined(__FreeBSD__) && !defined(__sun) && !defined(__NetBSD__) // avoid unused warnings
|
||||
#else
|
||||
|
||||
static int mi_prim_open(const char* fpath, int open_flags) {
|
||||
static inline int mi_prim_open(const char* fpath, int open_flags) {
|
||||
return open(fpath,open_flags);
|
||||
}
|
||||
static ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
|
||||
static inline ssize_t mi_prim_read(int fd, void* buf, size_t bufsize) {
|
||||
return read(fd,buf,bufsize);
|
||||
}
|
||||
static int mi_prim_close(int fd) {
|
||||
static inline int mi_prim_close(int fd) {
|
||||
return close(fd);
|
||||
}
|
||||
static int mi_prim_access(const char *fpath, int mode) {
|
||||
static inline int mi_prim_access(const char *fpath, int mode) {
|
||||
return access(fpath,mode);
|
||||
}
|
||||
|
||||
@@ -102,40 +121,96 @@ static int mi_prim_access(const char *fpath, int mode) {
|
||||
|
||||
static bool unix_detect_overcommit(void) {
|
||||
bool os_overcommit = true;
|
||||
#if defined(__linux__)
|
||||
int fd = mi_prim_open("/proc/sys/vm/overcommit_memory", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
char buf[32] = {0};
|
||||
ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));
|
||||
mi_prim_close(fd);
|
||||
// <https://www.kernel.org/doc/Documentation/vm/overcommit-accounting>
|
||||
// 0: heuristic overcommit, 1: always overcommit, 2: never overcommit (ignore NORESERVE)
|
||||
if (nread >= 1) {
|
||||
os_overcommit = (buf[0] == '0' || buf[0] == '1');
|
||||
#if defined(__linux__)
|
||||
int fd = mi_prim_open("/proc/sys/vm/overcommit_memory", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
char buf[32] = {0};
|
||||
ssize_t nread = mi_prim_read(fd, &buf, sizeof(buf));
|
||||
mi_prim_close(fd);
|
||||
// <https://www.kernel.org/doc/Documentation/vm/overcommit-accounting>
|
||||
// 0: heuristic overcommit, 1: always overcommit, 2: never overcommit (ignore NORESERVE)
|
||||
if (nread >= 1) {
|
||||
os_overcommit = (buf[0] == '0' || buf[0] == '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
int val = 0;
|
||||
size_t olen = sizeof(val);
|
||||
if (sysctlbyname("vm.overcommit", &val, &olen, NULL, 0) == 0) {
|
||||
os_overcommit = (val != 0);
|
||||
}
|
||||
#else
|
||||
// default: overcommit is true
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
int val = 0;
|
||||
size_t olen = sizeof(val);
|
||||
if (sysctlbyname("vm.overcommit", &val, &olen, NULL, 0) == 0) {
|
||||
os_overcommit = (val != 0);
|
||||
}
|
||||
#else
|
||||
// default: overcommit is true
|
||||
#endif
|
||||
return os_overcommit;
|
||||
}
|
||||
|
||||
void _mi_prim_mem_init( mi_os_mem_config_t* config ) {
|
||||
// try to detect the physical memory dynamically (if possible)
|
||||
static void unix_detect_physical_memory( size_t page_size, size_t* physical_memory_in_kib ) {
|
||||
#if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE)) // freeBSD, macOS
|
||||
MI_UNUSED(page_size);
|
||||
int64_t physical_memory = 0;
|
||||
size_t length = sizeof(int64_t);
|
||||
#if defined(HW_PHYSMEM64)
|
||||
int mib[2] = { CTL_HW, HW_PHYSMEM64 };
|
||||
#else
|
||||
int mib[2] = { CTL_HW, HW_MEMSIZE };
|
||||
#endif
|
||||
const int err = sysctl(mib, 2, &physical_memory, &length, NULL, 0);
|
||||
if (err==0 && physical_memory > 0) {
|
||||
const int64_t phys_in_kib = physical_memory / MI_KiB;
|
||||
if (phys_in_kib > 0 && (uint64_t)phys_in_kib <= SIZE_MAX) {
|
||||
*physical_memory_in_kib = (size_t)phys_in_kib;
|
||||
}
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
MI_UNUSED(page_size);
|
||||
struct sysinfo info; _mi_memzero_var(info);
|
||||
const int err = sysinfo(&info);
|
||||
if (err==0 && info.mem_unit > 0 && info.totalram <= SIZE_MAX) {
|
||||
if (info.mem_unit==MI_KiB) {
|
||||
*physical_memory_in_kib = (size_t)info.totalram;
|
||||
}
|
||||
else {
|
||||
size_t total = 0;
|
||||
if (!mi_mul_overflow((size_t)info.totalram, (size_t)info.mem_unit, &total)) {
|
||||
*physical_memory_in_kib = (total / MI_KiB);
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif defined(_SC_PHYS_PAGES) // do not use by default as it might cause allocation (by using `fopen` to parse /proc/meminfo) (issue #1100)
|
||||
const long pphys = sysconf(_SC_PHYS_PAGES);
|
||||
const size_t psize_in_kib = page_size / MI_KiB;
|
||||
if (psize_in_kib > 0 && pphys > 0 && (unsigned long)pphys <= SIZE_MAX && (size_t)pphys <= (SIZE_MAX/psize_in_kib)) {
|
||||
*physical_memory_in_kib = (size_t)pphys * psize_in_kib;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void _mi_prim_mem_init( mi_os_mem_config_t* config )
|
||||
{
|
||||
long psize = sysconf(_SC_PAGESIZE);
|
||||
if (psize > 0) {
|
||||
if (psize > 0 && (unsigned long)psize < SIZE_MAX) {
|
||||
config->page_size = (size_t)psize;
|
||||
config->alloc_granularity = (size_t)psize;
|
||||
unix_detect_physical_memory(config->page_size, &config->physical_memory_in_kib);
|
||||
}
|
||||
config->large_page_size = 2*MI_MiB; // TODO: can we query the OS for this?
|
||||
config->large_page_size = MI_UNIX_LARGE_PAGE_SIZE;
|
||||
config->has_overcommit = unix_detect_overcommit();
|
||||
config->must_free_whole = false; // mmap can free in parts
|
||||
config->has_partial_free = true; // mmap can free in parts
|
||||
config->has_virtual_reserve = true; // todo: check if this true for NetBSD? (for anonymous mmap with PROT_NONE)
|
||||
|
||||
// disable transparent huge pages for this process?
|
||||
#if (defined(__linux__) || defined(__ANDROID__)) && defined(PR_GET_THP_DISABLE)
|
||||
if (!mi_option_is_enabled(mi_option_allow_thp)) // disable THP if requested through an option
|
||||
{
|
||||
if (prctl(PR_GET_THP_DISABLE, 0, 0, 0, 0) == 0) { // -1 on error, 1 if already disabled
|
||||
// Most likely since distros often come with always/madvise settings.
|
||||
// Disabling only for mimalloc process rather than touching system wide settings
|
||||
(void)prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +219,7 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config ) {
|
||||
//---------------------------------------------
|
||||
|
||||
int _mi_prim_free(void* addr, size_t size ) {
|
||||
if (size==0) return 0;
|
||||
bool err = (munmap(addr, size) == -1);
|
||||
return (err ? errno : 0);
|
||||
}
|
||||
@@ -153,25 +229,40 @@ int _mi_prim_free(void* addr, size_t size ) {
|
||||
// mmap
|
||||
//---------------------------------------------
|
||||
|
||||
// return errno on failure
|
||||
static int unix_madvise(void* addr, size_t size, int advice) {
|
||||
#if defined(__sun)
|
||||
return madvise((caddr_t)addr, size, advice); // Solaris needs cast (issue #520)
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
const int res = madvise((caddr_t)addr, size, advice); // Solaris needs cast (issue #520)
|
||||
return (res==0 ? 0 : errno);
|
||||
#elif defined(__QNX__)
|
||||
return posix_madvise(addr, size, advice); // posix returns errno
|
||||
#else
|
||||
return madvise(addr, size, advice);
|
||||
const int res = madvise(addr, size, advice); // linux returns -1 on failure and sets errno
|
||||
return (res==0 ? 0 : errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int protect_flags, int flags, int fd) {
|
||||
static void* unix_mmap_prim(void* addr, size_t size, int protect_flags, int flags, int fd) {
|
||||
void* p = mmap(addr, size, protect_flags, flags, fd, 0 /* offset */);
|
||||
#if defined(__linux__) && defined(PR_SET_VMA)
|
||||
if (p!=MAP_FAILED && p!=NULL) {
|
||||
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, p, size, "mimalloc");
|
||||
}
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* unix_mmap_prim_aligned(void* addr, size_t size, size_t try_alignment, int protect_flags, int flags, int fd) {
|
||||
MI_UNUSED(try_alignment);
|
||||
void* p = NULL;
|
||||
#if defined(MAP_ALIGNED) // BSD
|
||||
if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {
|
||||
size_t n = mi_bsr(try_alignment);
|
||||
if (((size_t)1 << n) == try_alignment && n >= 12 && n <= 30) { // alignment is a power of 2 and 4096 <= alignment <= 1GiB
|
||||
p = mmap(addr, size, protect_flags, flags | MAP_ALIGNED(n), fd, 0);
|
||||
p = unix_mmap_prim(addr, size, protect_flags, flags | MAP_ALIGNED(n), fd);
|
||||
if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {
|
||||
int err = errno;
|
||||
_mi_verbose_message("unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, addr);
|
||||
_mi_trace_message("unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, addr);
|
||||
}
|
||||
if (p!=MAP_FAILED) return p;
|
||||
// fall back to regular mmap
|
||||
@@ -179,7 +270,7 @@ static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int p
|
||||
}
|
||||
#elif defined(MAP_ALIGN) // Solaris
|
||||
if (addr == NULL && try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0) {
|
||||
p = mmap((void*)try_alignment, size, protect_flags, flags | MAP_ALIGN, fd, 0); // addr parameter is the required alignment
|
||||
p = unix_mmap_prim((void*)try_alignment, size, protect_flags, flags | MAP_ALIGN, fd); // addr parameter is the required alignment
|
||||
if (p!=MAP_FAILED) return p;
|
||||
// fall back to regular mmap
|
||||
}
|
||||
@@ -189,14 +280,14 @@ static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int p
|
||||
if (addr == NULL) {
|
||||
void* hint = _mi_os_get_aligned_hint(try_alignment, size);
|
||||
if (hint != NULL) {
|
||||
p = mmap(hint, size, protect_flags, flags, fd, 0);
|
||||
p = unix_mmap_prim(hint, size, protect_flags, flags, fd);
|
||||
if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) {
|
||||
#if MI_TRACK_ENABLED // asan sometimes does not instrument errno correctly?
|
||||
int err = 0;
|
||||
#else
|
||||
int err = errno;
|
||||
#endif
|
||||
_mi_verbose_message("unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, hint);
|
||||
_mi_trace_message("unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, hint);
|
||||
}
|
||||
if (p!=MAP_FAILED) return p;
|
||||
// fall back to regular mmap
|
||||
@@ -204,7 +295,7 @@ static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int p
|
||||
}
|
||||
#endif
|
||||
// regular mmap
|
||||
p = mmap(addr, size, protect_flags, flags, fd, 0);
|
||||
p = unix_mmap_prim(addr, size, protect_flags, flags, fd);
|
||||
if (p!=MAP_FAILED) return p;
|
||||
// failed to allocate
|
||||
return NULL;
|
||||
@@ -214,13 +305,17 @@ static int unix_mmap_fd(void) {
|
||||
#if defined(VM_MAKE_TAG)
|
||||
// macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99)
|
||||
int os_tag = (int)mi_option_get(mi_option_os_tag);
|
||||
if (os_tag < 100 || os_tag > 255) { os_tag = 100; }
|
||||
if (os_tag < 100 || os_tag > 255) { os_tag = 254; }
|
||||
return VM_MAKE_TAG(os_tag);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MAP_ALIGNED_SUPER) || defined(MAP_HUGETLB) || defined(MAP_HUGE_1GB) || defined(MAP_HUGE_2MB) || defined(VM_FLAGS_SUPERPAGE_SIZE_2MB)
|
||||
#define MI_OS_HAS_HUGE_PAGES 1
|
||||
#endif
|
||||
|
||||
static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) {
|
||||
#if !defined(MAP_ANONYMOUS)
|
||||
#define MAP_ANONYMOUS MAP_ANON
|
||||
@@ -238,7 +333,8 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
|
||||
protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD
|
||||
#endif
|
||||
// huge page allocation
|
||||
if ((large_only || _mi_os_use_large_page(size, try_alignment)) && allow_large) {
|
||||
#if MI_OS_HAS_HUGE_PAGES
|
||||
if (allow_large && (large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages)))) {
|
||||
static _Atomic(size_t) large_page_try_ok; // = 0;
|
||||
size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok);
|
||||
if (!large_only && try_ok > 0) {
|
||||
@@ -258,8 +354,8 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
|
||||
lflags |= MAP_HUGETLB;
|
||||
#endif
|
||||
#ifdef MAP_HUGE_1GB
|
||||
static bool mi_huge_pages_available = true;
|
||||
if ((size % MI_GiB) == 0 && mi_huge_pages_available) {
|
||||
static _Atomic(size_t) mi_huge_1gib_pages_unavailable;
|
||||
if (large_only && (size % MI_GiB) == 0 && (mi_atomic_load_relaxed(&mi_huge_1gib_pages_unavailable)==0)) {
|
||||
lflags |= MAP_HUGE_1GB;
|
||||
}
|
||||
else
|
||||
@@ -275,13 +371,15 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
|
||||
if (large_only || lflags != flags) {
|
||||
// try large OS page allocation
|
||||
*is_large = true;
|
||||
p = unix_mmap_prim(addr, size, try_alignment, protect_flags, lflags, lfd);
|
||||
p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, lflags, lfd);
|
||||
#ifdef MAP_HUGE_1GB
|
||||
if (p == NULL && (lflags & MAP_HUGE_1GB) != 0) {
|
||||
mi_huge_pages_available = false; // don't try huge 1GiB pages again
|
||||
_mi_warning_message("unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (errno: %i)\n", errno);
|
||||
if (p == NULL && (lflags & MAP_HUGE_1GB) == MAP_HUGE_1GB) {
|
||||
mi_atomic_store_relaxed(&mi_huge_1gib_pages_unavailable,1); // don't try huge 1GiB pages again
|
||||
if (large_only) {
|
||||
_mi_warning_message("unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (errno: %i)\n", errno);
|
||||
}
|
||||
lflags = ((lflags & ~MAP_HUGE_1GB) | MAP_HUGE_2MB);
|
||||
p = unix_mmap_prim(addr, size, try_alignment, protect_flags, lflags, lfd);
|
||||
p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, lflags, lfd);
|
||||
}
|
||||
#endif
|
||||
if (large_only) return p;
|
||||
@@ -290,12 +388,14 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // huge pages
|
||||
#endif
|
||||
// regular allocation
|
||||
if (p == NULL) {
|
||||
*is_large = false;
|
||||
p = unix_mmap_prim(addr, size, try_alignment, protect_flags, flags, fd);
|
||||
if (p != NULL) {
|
||||
p = unix_mmap_prim_aligned(addr, size, try_alignment, protect_flags, flags, fd);
|
||||
#if !defined(MI_NO_THP)
|
||||
if (p != NULL && mi_option_is_enabled(mi_option_allow_thp) && _mi_os_canuse_large_page(size, try_alignment)) {
|
||||
#if defined(MADV_HUGEPAGE)
|
||||
// Many Linux systems don't allow MAP_HUGETLB but they support instead
|
||||
// transparent huge pages (THP). Generally, it is not required to call `madvise` with MADV_HUGE
|
||||
@@ -303,35 +403,31 @@ static void* unix_mmap(void* addr, size_t size, size_t try_alignment, int protec
|
||||
// in that case -- in particular for our large regions (in `memory.c`).
|
||||
// However, some systems only allow THP if called with explicit `madvise`, so
|
||||
// when large OS pages are enabled for mimalloc, we call `madvise` anyways.
|
||||
if (allow_large && _mi_os_use_large_page(size, try_alignment)) {
|
||||
if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {
|
||||
*is_large = true; // possibly
|
||||
};
|
||||
}
|
||||
if (unix_madvise(p, size, MADV_HUGEPAGE) == 0) {
|
||||
// *is_large = true; // possibly
|
||||
};
|
||||
#elif defined(__sun)
|
||||
if (allow_large && _mi_os_use_large_page(size, try_alignment)) {
|
||||
struct memcntl_mha cmd = {0};
|
||||
cmd.mha_pagesize = 2*MI_MiB;
|
||||
cmd.mha_cmd = MHA_MAPSIZE_VA;
|
||||
if (memcntl((caddr_t)p, size, MC_HAT_ADVISE, (caddr_t)&cmd, 0, 0) == 0) {
|
||||
*is_large = true;
|
||||
}
|
||||
struct memcntl_mha cmd = {0};
|
||||
cmd.mha_pagesize = _mi_os_large_page_size();
|
||||
cmd.mha_cmd = MHA_MAPSIZE_VA;
|
||||
if (memcntl((caddr_t)p, size, MC_HAT_ADVISE, (caddr_t)&cmd, 0, 0) == 0) {
|
||||
// *is_large = true; // possibly
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
|
||||
int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
|
||||
int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
|
||||
mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0);
|
||||
mi_assert_internal(commit || !allow_large);
|
||||
mi_assert_internal(try_alignment > 0);
|
||||
|
||||
*is_zero = true;
|
||||
int protect_flags = (commit ? (PROT_WRITE | PROT_READ) : PROT_NONE);
|
||||
*addr = unix_mmap(NULL, size, try_alignment, protect_flags, false, allow_large, is_large);
|
||||
*addr = unix_mmap(hint_addr, size, try_alignment, protect_flags, false, allow_large, is_large);
|
||||
return (*addr != NULL ? 0 : errno);
|
||||
}
|
||||
|
||||
@@ -341,7 +437,7 @@ int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_la
|
||||
//---------------------------------------------
|
||||
|
||||
static void unix_mprotect_hint(int err) {
|
||||
#if defined(__linux__) && (MI_SECURE>=2) // guard page around every mimalloc page
|
||||
#if defined(__linux__) && (MI_SECURE>=5) // guard page around every mimalloc page
|
||||
if (err == ENOMEM) {
|
||||
_mi_warning_message("The next warning may be caused by a low memory map limit.\n"
|
||||
" On Linux this is controlled by the vm.max_map_count -- maybe increase it?\n"
|
||||
@@ -367,11 +463,25 @@ int _mi_prim_commit(void* start, size_t size, bool* is_zero) {
|
||||
return err;
|
||||
}
|
||||
|
||||
int _mi_prim_reuse(void* start, size_t size) {
|
||||
MI_UNUSED(start); MI_UNUSED(size);
|
||||
#if defined(__APPLE__) && defined(MADV_FREE_REUSE)
|
||||
return unix_madvise(start, size, MADV_FREE_REUSE);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
|
||||
int err = 0;
|
||||
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
|
||||
err = unix_madvise(start, size, MADV_DONTNEED);
|
||||
#if !MI_DEBUG && !MI_SECURE
|
||||
#if defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
|
||||
// decommit on macOS: use MADV_FREE_REUSABLE as it does immediate rss accounting (issue #1097)
|
||||
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
|
||||
if (err) { err = unix_madvise(start, size, MADV_DONTNEED); }
|
||||
#else
|
||||
// decommit: use MADV_DONTNEED as it decreases rss immediately (unlike MADV_FREE)
|
||||
err = unix_madvise(start, size, MADV_DONTNEED);
|
||||
#endif
|
||||
#if !MI_DEBUG && MI_SECURE<=2
|
||||
*needs_recommit = false;
|
||||
#else
|
||||
*needs_recommit = true;
|
||||
@@ -388,22 +498,30 @@ int _mi_prim_decommit(void* start, size_t size, bool* needs_recommit) {
|
||||
}
|
||||
|
||||
int _mi_prim_reset(void* start, size_t size) {
|
||||
// We try to use `MADV_FREE` as that is the fastest. A drawback though is that it
|
||||
int err = 0;
|
||||
|
||||
// on macOS can use MADV_FREE_REUSABLE (but we disable this for now as it seems slower)
|
||||
#if 0 && defined(__APPLE__) && defined(MADV_FREE_REUSABLE)
|
||||
err = unix_madvise(start, size, MADV_FREE_REUSABLE);
|
||||
if (err==0) return 0;
|
||||
// fall through
|
||||
#endif
|
||||
|
||||
#if defined(MADV_FREE)
|
||||
// Otherwise, we try to use `MADV_FREE` as that is the fastest. A drawback though is that it
|
||||
// will not reduce the `rss` stats in tools like `top` even though the memory is available
|
||||
// to other processes. With the default `MIMALLOC_PURGE_DECOMMITS=1` we ensure that by
|
||||
// default `MADV_DONTNEED` is used though.
|
||||
#if defined(MADV_FREE)
|
||||
static _Atomic(size_t) advice = MI_ATOMIC_VAR_INIT(MADV_FREE);
|
||||
int oadvice = (int)mi_atomic_load_relaxed(&advice);
|
||||
int err;
|
||||
while ((err = unix_madvise(start, size, oadvice)) != 0 && errno == EAGAIN) { errno = 0; };
|
||||
if (err != 0 && errno == EINVAL && oadvice == MADV_FREE) {
|
||||
while ((err = unix_madvise(start, size, oadvice)) != 0 && err == EAGAIN) { /* try again */ };
|
||||
if (err == EINVAL && oadvice == MADV_FREE) {
|
||||
// if MADV_FREE is not supported, fall back to MADV_DONTNEED from now on
|
||||
mi_atomic_store_release(&advice, (size_t)MADV_DONTNEED);
|
||||
err = unix_madvise(start, size, MADV_DONTNEED);
|
||||
}
|
||||
#else
|
||||
int err = unix_madvise(start, size, MADV_DONTNEED);
|
||||
err = unix_madvise(start, size, MADV_DONTNEED);
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
@@ -442,7 +560,7 @@ int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bo
|
||||
bool is_large = true;
|
||||
*is_zero = true;
|
||||
*addr = unix_mmap(hint_addr, size, MI_SEGMENT_SIZE, PROT_READ | PROT_WRITE, true, true, &is_large);
|
||||
if (*addr != NULL && numa_node >= 0 && numa_node < 8*MI_INTPTR_SIZE) { // at most 64 nodes
|
||||
if (*addr != NULL && numa_node >= 0 && numa_node < (8*MI_INTPTR_SIZE - 1)) { // at most 63 nodes
|
||||
unsigned long numa_mask = (1UL << numa_node);
|
||||
// TODO: does `mbind` work correctly for huge OS pages? should we
|
||||
// use `set_mempolicy` before calling mmap instead?
|
||||
@@ -450,7 +568,7 @@ int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bo
|
||||
long err = mi_prim_mbind(*addr, size, MPOL_PREFERRED, &numa_mask, 8*MI_INTPTR_SIZE, 0);
|
||||
if (err != 0) {
|
||||
err = errno;
|
||||
_mi_warning_message("failed to bind huge (1GiB) pages to numa node %d (error: %d (0x%x))\n", numa_node, err, err);
|
||||
_mi_warning_message("failed to bind huge (1GiB) pages to numa node %d (error: %ld (0x%lx))\n", numa_node, err, err);
|
||||
}
|
||||
}
|
||||
return (*addr != NULL ? 0 : errno);
|
||||
@@ -473,13 +591,11 @@ int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bo
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <stdio.h> // snprintf
|
||||
|
||||
size_t _mi_prim_numa_node(void) {
|
||||
#if defined(MI_HAS_SYSCALL_H) && defined(SYS_getcpu)
|
||||
unsigned long node = 0;
|
||||
unsigned long ncpu = 0;
|
||||
long err = syscall(SYS_getcpu, &ncpu, &node, NULL);
|
||||
unsigned int node = 0;
|
||||
unsigned int ncpu = 0;
|
||||
int err = syscall(SYS_getcpu, &ncpu, &node, NULL);
|
||||
if (err != 0) return 0;
|
||||
return node;
|
||||
#else
|
||||
@@ -489,13 +605,16 @@ size_t _mi_prim_numa_node(void) {
|
||||
|
||||
size_t _mi_prim_numa_node_count(void) {
|
||||
char buf[128];
|
||||
unsigned node = 0;
|
||||
for(node = 0; node < 256; node++) {
|
||||
unsigned last_found = 0;
|
||||
for(unsigned node = 1; node < 256; node++) {
|
||||
// enumerate node entries -- todo: it there a more efficient way to do this? (but ensure there is no allocation)
|
||||
snprintf(buf, 127, "/sys/devices/system/node/node%u", node + 1);
|
||||
if (mi_prim_access(buf,R_OK) != 0) break;
|
||||
_mi_snprintf(buf, 127, "/sys/devices/system/node/node%u", node);
|
||||
if (mi_prim_access(buf,R_OK) != 0) {
|
||||
if (node - last_found > 4) break; // allow some sparseness of nodes but not more than 4
|
||||
}
|
||||
else { last_found = node; } // highest found node
|
||||
}
|
||||
return (node+1);
|
||||
return last_found + 1;
|
||||
}
|
||||
|
||||
#elif defined(__FreeBSD__) && __FreeBSD_version >= 1200000
|
||||
@@ -551,34 +670,38 @@ size_t _mi_prim_numa_node_count(void) {
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
|
||||
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
struct timespec t;
|
||||
#ifdef CLOCK_MONOTONIC
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &t);
|
||||
#endif
|
||||
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// low resolution timer
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
#if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)
|
||||
return (mi_msecs_t)clock();
|
||||
#elif (CLOCKS_PER_SEC < 1000)
|
||||
return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
|
||||
static mi_msecs_t mi_prim_clock_now_lowres(void) {
|
||||
const int64_t ticks = (int64_t)clock();
|
||||
#if !defined(CLOCKS_PER_SEC)
|
||||
return ticks;
|
||||
#else
|
||||
return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
|
||||
if (CLOCKS_PER_SEC <= 0 || CLOCKS_PER_SEC == 1000) {
|
||||
return ticks;
|
||||
}
|
||||
else if (CLOCKS_PER_SEC > 0 && CLOCKS_PER_SEC < 1000) {
|
||||
return ticks * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
|
||||
}
|
||||
else {
|
||||
return ticks / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
|
||||
#ifdef CLOCK_MONOTONIC
|
||||
const clockid_t clockid = CLOCK_MONOTONIC;
|
||||
#else
|
||||
const clockid_t clockid = CLOCK_REALTIME;
|
||||
#endif
|
||||
struct timespec t;
|
||||
if (clock_gettime(clockid,&t) == 0) {
|
||||
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000L);
|
||||
}
|
||||
#endif
|
||||
return mi_prim_clock_now_lowres();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
@@ -605,41 +728,46 @@ static mi_msecs_t timeval_secs(const struct timeval* tv) {
|
||||
void _mi_prim_process_info(mi_process_info_t* pinfo)
|
||||
{
|
||||
struct rusage rusage;
|
||||
getrusage(RUSAGE_SELF, &rusage);
|
||||
pinfo->utime = timeval_secs(&rusage.ru_utime);
|
||||
pinfo->stime = timeval_secs(&rusage.ru_stime);
|
||||
#if !defined(__HAIKU__)
|
||||
pinfo->page_faults = rusage.ru_majflt;
|
||||
#endif
|
||||
#if defined(__HAIKU__)
|
||||
// Haiku does not have (yet?) a way to
|
||||
// get these stats per process
|
||||
thread_info tid;
|
||||
area_info mem;
|
||||
ssize_t c;
|
||||
get_thread_info(find_thread(0), &tid);
|
||||
while (get_next_area_info(tid.team, &c, &mem) == B_OK) {
|
||||
pinfo->peak_rss += mem.ram_size;
|
||||
}
|
||||
pinfo->page_faults = 0;
|
||||
#elif defined(__APPLE__)
|
||||
pinfo->peak_rss = rusage.ru_maxrss; // macos reports in bytes
|
||||
#ifdef MACH_TASK_BASIC_INFO
|
||||
struct mach_task_basic_info info;
|
||||
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
||||
pinfo->current_rss = (size_t)info.resident_size;
|
||||
}
|
||||
#else
|
||||
struct task_basic_info info;
|
||||
mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
||||
pinfo->current_rss = (size_t)info.resident_size;
|
||||
if (getrusage(RUSAGE_SELF, &rusage) == 0) {
|
||||
pinfo->utime = timeval_secs(&rusage.ru_utime);
|
||||
pinfo->stime = timeval_secs(&rusage.ru_stime);
|
||||
#if !defined(__HAIKU__)
|
||||
pinfo->page_faults = rusage.ru_majflt;
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
pinfo->peak_rss = rusage.ru_maxrss; // macos reports in bytes
|
||||
#else
|
||||
pinfo->peak_rss = rusage.ru_maxrss * 1024; // Linux/BSD report in KiB
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__HAIKU__)
|
||||
// Haiku does not have (yet?) a way to
|
||||
// get these stats per process
|
||||
thread_info tid;
|
||||
if (get_thread_info(find_thread(0), &tid) == B_OK) {
|
||||
area_info mem;
|
||||
ssize_t c;
|
||||
while (get_next_area_info(tid.team, &c, &mem) == B_OK) {
|
||||
pinfo->peak_rss += mem.ram_size;
|
||||
}
|
||||
}
|
||||
pinfo->page_faults = 0;
|
||||
#elif defined(__APPLE__)
|
||||
#ifdef MACH_TASK_BASIC_INFO
|
||||
struct mach_task_basic_info info;
|
||||
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
||||
pinfo->current_rss = (size_t)info.resident_size;
|
||||
}
|
||||
#else
|
||||
struct task_basic_info info;
|
||||
mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) {
|
||||
pinfo->current_rss = (size_t)info.resident_size;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
pinfo->peak_rss = rusage.ru_maxrss * 1024; // Linux/BSD report in KiB
|
||||
#endif
|
||||
// use defaults for commit
|
||||
}
|
||||
|
||||
@@ -686,28 +814,28 @@ static char** mi_get_environ(void) {
|
||||
return environ;
|
||||
}
|
||||
#endif
|
||||
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
if (name==NULL) return false;
|
||||
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
if (name==NULL) return -1;
|
||||
const size_t len = _mi_strlen(name);
|
||||
if (len == 0) return false;
|
||||
if (len == 0) return -1;
|
||||
char** env = mi_get_environ();
|
||||
if (env == NULL) return false;
|
||||
if (env == NULL) return -1;
|
||||
// compare up to 10000 entries
|
||||
for (int i = 0; i < 10000 && env[i] != NULL; i++) {
|
||||
const char* s = env[i];
|
||||
if (_mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive
|
||||
// found it
|
||||
_mi_strlcpy(result, s + len + 1, result_size);
|
||||
return true;
|
||||
if (!_mi_strlcpy(result, s + len + 1, result_size)) return -1;
|
||||
return 1; // success
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return 0; // not found
|
||||
}
|
||||
#else
|
||||
// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime
|
||||
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
// cannot call getenv() when still initializing the C runtime.
|
||||
if (_mi_preloading()) return false;
|
||||
if (_mi_preloading()) return -1; // error, try again later
|
||||
const char* s = getenv(name);
|
||||
if (s == NULL) {
|
||||
// we check the upper case name too.
|
||||
@@ -719,9 +847,9 @@ bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
buf[len] = 0;
|
||||
s = getenv(buf);
|
||||
}
|
||||
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return false;
|
||||
_mi_strlcpy(result, s, result_size);
|
||||
return true;
|
||||
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return 0; // not found
|
||||
if (!_mi_strlcpy(result, s, result_size)) return -1;
|
||||
return 1; // success
|
||||
}
|
||||
#endif // !MI_USE_ENVIRON
|
||||
|
||||
@@ -730,40 +858,30 @@ bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
// Random
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
#if defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
|
||||
#if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_15) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15)
|
||||
#include <CommonCrypto/CommonCryptoError.h>
|
||||
#include <CommonCrypto/CommonRandom.h>
|
||||
#endif
|
||||
|
||||
bool _mi_prim_random_buf(void* buf, size_t buf_len) {
|
||||
#if defined(MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
|
||||
// We prefer CCRandomGenerateBytes as it returns an error code while arc4random_buf
|
||||
// may fail silently on macOS. See PR #390, and <https://opensource.apple.com/source/Libc/Libc-1439.40.11/gen/FreeBSD/arc4random.c.auto.html>
|
||||
return (CCRandomGenerateBytes(buf, buf_len) == kCCSuccess);
|
||||
#else
|
||||
// fall back on older macOS
|
||||
arc4random_buf(buf, buf_len);
|
||||
return true;
|
||||
#endif
|
||||
// We prefer CCRandomGenerateBytes as it returns an error code while arc4random_buf
|
||||
// may fail silently on macOS. See PR #390, and <https://opensource.apple.com/source/Libc/Libc-1439.40.11/gen/FreeBSD/arc4random.c.auto.html>
|
||||
return (CCRandomGenerateBytes(buf, buf_len) == kCCSuccess);
|
||||
}
|
||||
|
||||
#elif defined(__ANDROID__) || defined(__DragonFly__) || \
|
||||
defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
|
||||
defined(__sun) || defined(__CYGWIN__)
|
||||
defined(__sun) || \
|
||||
(defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7))
|
||||
|
||||
#include <stdlib.h>
|
||||
bool _mi_prim_random_buf(void* buf, size_t buf_len) {
|
||||
arc4random_buf(buf, buf_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(__linux__) || defined(__HAIKU__)
|
||||
#elif defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__) // also for old apple versions < 10.7 (issue #829)
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
bool _mi_prim_random_buf(void* buf, size_t buf_len) {
|
||||
@@ -792,7 +910,10 @@ bool _mi_prim_random_buf(void* buf, size_t buf_len) {
|
||||
size_t count = 0;
|
||||
while(count < buf_len) {
|
||||
ssize_t ret = mi_prim_read(fd, (char*)buf + count, buf_len - count);
|
||||
if (ret<=0) {
|
||||
if (ret==0) {
|
||||
break;
|
||||
}
|
||||
else if (ret<0) {
|
||||
if (errno!=EAGAIN && errno!=EINTR) break;
|
||||
}
|
||||
else {
|
||||
@@ -830,11 +951,17 @@ static void mi_pthread_done(void* value) {
|
||||
|
||||
void _mi_prim_thread_init_auto_done(void) {
|
||||
mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1));
|
||||
pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
|
||||
const int err = pthread_key_create(&_mi_heap_default_key, &mi_pthread_done);
|
||||
if (err!=0) {
|
||||
_mi_error_message(err,"unable to create a pthread thread local key (error %d (0x%x))", err, err);
|
||||
_mi_heap_default_key = (pthread_key_t)(-1);
|
||||
};
|
||||
}
|
||||
|
||||
void _mi_prim_thread_done_auto_done(void) {
|
||||
// nothing to do
|
||||
if (_mi_heap_default_key != (pthread_key_t)(-1)) { // do not leak the key, see issue #809
|
||||
pthread_key_delete(_mi_heap_default_key);
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) {
|
||||
|
||||
@@ -9,9 +9,10 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
|
||||
#include "mimalloc.h"
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h"
|
||||
#include <unistd.h> // sbrk()
|
||||
|
||||
#include <stdio.h> // fputs
|
||||
#include <stdlib.h> // getenv
|
||||
|
||||
//---------------------------------------------
|
||||
// Initialize
|
||||
@@ -21,7 +22,7 @@ void _mi_prim_mem_init( mi_os_mem_config_t* config ) {
|
||||
config->page_size = 64*MI_KiB; // WebAssembly has a fixed page size: 64KiB
|
||||
config->alloc_granularity = 16;
|
||||
config->has_overcommit = false;
|
||||
config->must_free_whole = true;
|
||||
config->has_partial_free = false;
|
||||
config->has_virtual_reserve = false;
|
||||
}
|
||||
|
||||
@@ -41,6 +42,8 @@ int _mi_prim_free(void* addr, size_t size ) {
|
||||
//---------------------------------------------
|
||||
|
||||
#if defined(MI_USE_SBRK)
|
||||
#include <unistd.h> // for sbrk
|
||||
|
||||
static void* mi_memory_grow( size_t size ) {
|
||||
void* p = sbrk(size);
|
||||
if (p == (void*)(-1)) return NULL;
|
||||
@@ -86,7 +89,7 @@ static void* mi_prim_mem_grow(size_t size, size_t try_alignment) {
|
||||
{
|
||||
void* current = mi_memory_grow(0); // get current size
|
||||
if (current != NULL) {
|
||||
void* aligned_current = mi_align_up_ptr(current, try_alignment); // and align from there to minimize wasted space
|
||||
void* aligned_current = _mi_align_up_ptr(current, try_alignment); // and align from there to minimize wasted space
|
||||
alloc_size = _mi_align_up( ((uint8_t*)aligned_current - (uint8_t*)current) + size, _mi_os_page_size());
|
||||
base = mi_memory_grow(alloc_size);
|
||||
}
|
||||
@@ -95,7 +98,7 @@ static void* mi_prim_mem_grow(size_t size, size_t try_alignment) {
|
||||
pthread_mutex_unlock(&mi_heap_grow_mutex);
|
||||
#endif
|
||||
if (base != NULL) {
|
||||
p = mi_align_up_ptr(base, try_alignment);
|
||||
p = _mi_align_up_ptr(base, try_alignment);
|
||||
if ((uint8_t*)p + size > (uint8_t*)base + alloc_size) {
|
||||
// another thread used wasm_memory_grow/sbrk in-between and we do not have enough
|
||||
// space after alignment. Give up (and waste the space as we cannot shrink :-( )
|
||||
@@ -116,8 +119,8 @@ static void* mi_prim_mem_grow(size_t size, size_t try_alignment) {
|
||||
}
|
||||
|
||||
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
|
||||
int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
|
||||
MI_UNUSED(allow_large); MI_UNUSED(commit);
|
||||
int _mi_prim_alloc(void* hint_addr, size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) {
|
||||
MI_UNUSED(allow_large); MI_UNUSED(commit); MI_UNUSED(hint_addr);
|
||||
*is_large = false;
|
||||
*is_zero = false;
|
||||
*addr = mi_prim_mem_grow(size, try_alignment);
|
||||
@@ -146,6 +149,11 @@ int _mi_prim_reset(void* addr, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_reuse(void* addr, size_t size) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _mi_prim_protect(void* addr, size_t size, bool protect) {
|
||||
MI_UNUSED(addr); MI_UNUSED(size); MI_UNUSED(protect);
|
||||
return 0;
|
||||
@@ -178,32 +186,38 @@ size_t _mi_prim_numa_node_count(void) {
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
|
||||
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
struct timespec t;
|
||||
#ifdef CLOCK_MONOTONIC
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &t);
|
||||
#endif
|
||||
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// low resolution timer
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
#if !defined(CLOCKS_PER_SEC) || (CLOCKS_PER_SEC == 1000) || (CLOCKS_PER_SEC == 0)
|
||||
return (mi_msecs_t)clock();
|
||||
#elif (CLOCKS_PER_SEC < 1000)
|
||||
return (mi_msecs_t)clock() * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
|
||||
static mi_msecs_t mi_prim_clock_now_lowres(void) {
|
||||
const int64_t ticks = (int64_t)clock();
|
||||
#if !defined(CLOCKS_PER_SEC)
|
||||
return ticks;
|
||||
#else
|
||||
return (mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
|
||||
if (CLOCKS_PER_SEC <= 0 || CLOCKS_PER_SEC == 1000) {
|
||||
return ticks;
|
||||
}
|
||||
else if (CLOCKS_PER_SEC > 0 && CLOCKS_PER_SEC < 1000) {
|
||||
return ticks * (1000 / (mi_msecs_t)CLOCKS_PER_SEC);
|
||||
}
|
||||
else {
|
||||
return ticks / ((mi_msecs_t)CLOCKS_PER_SEC / 1000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
mi_msecs_t _mi_prim_clock_now(void) {
|
||||
#if defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC)
|
||||
#ifdef CLOCK_MONOTONIC
|
||||
const clockid_t clockid = CLOCK_MONOTONIC;
|
||||
#else
|
||||
const clockid_t clockid = CLOCK_REALTIME;
|
||||
#endif
|
||||
struct timespec t;
|
||||
if (clock_gettime(clockid,&t) == 0) {
|
||||
return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000L);
|
||||
}
|
||||
#endif
|
||||
return mi_prim_clock_now_lowres();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
||||
@@ -230,9 +244,9 @@ void _mi_prim_out_stderr( const char* msg ) {
|
||||
// Environment
|
||||
//----------------------------------------------------------------
|
||||
|
||||
bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
int _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
// cannot call getenv() when still initializing the C runtime.
|
||||
if (_mi_preloading()) return false;
|
||||
if (_mi_preloading()) return -1; // error, try again later
|
||||
const char* s = getenv(name);
|
||||
if (s == NULL) {
|
||||
// we check the upper case name too.
|
||||
@@ -244,9 +258,9 @@ bool _mi_prim_getenv(const char* name, char* result, size_t result_size) {
|
||||
buf[len] = 0;
|
||||
s = getenv(buf);
|
||||
}
|
||||
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return false;
|
||||
if (s == NULL || _mi_strnlen(s,result_size) >= result_size) return 0; // not found
|
||||
_mi_strlcpy(result, s, result_size);
|
||||
return true;
|
||||
return 1; // found
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ extern "C" {
|
||||
// - MCGEN_EVENTSETINFORMATION
|
||||
// - MCGEN_EVENTWRITETRANSFER
|
||||
//
|
||||
// If the macro is undefined, the MC implementation will default to the
|
||||
// If the the macro is undefined, the MC implementation will default to the
|
||||
// corresponding ETW APIs. For example, if the MCGEN_EVENTREGISTER macro is
|
||||
// undefined, the EventRegister[MyProviderName] macro will use EventRegister
|
||||
// in user mode and will use EtwRegister in kernel mode.
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+23
-13
@@ -99,7 +99,8 @@ static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t no
|
||||
// since we only use chacha for randomness (and not encryption) we
|
||||
// do not _need_ to read 32-bit values as little endian but we do anyways
|
||||
// just for being compatible :-)
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->output_available = 0;
|
||||
_mi_memzero(ctx->output,sizeof(ctx->output));
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
const uint8_t* sigma = (uint8_t*)"expand 32-byte k";
|
||||
ctx->input[i] = read32(sigma,i);
|
||||
@@ -114,7 +115,8 @@ static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t no
|
||||
}
|
||||
|
||||
static void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) {
|
||||
memset(ctx_new, 0, sizeof(*ctx_new));
|
||||
_mi_memzero(ctx_new, sizeof(*ctx_new));
|
||||
ctx_new->weak = ctx->weak;
|
||||
_mi_memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input));
|
||||
ctx_new->input[12] = 0;
|
||||
ctx_new->input[13] = 0;
|
||||
@@ -138,18 +140,23 @@ static bool mi_random_is_initialized(mi_random_ctx_t* ctx) {
|
||||
void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) {
|
||||
mi_assert_internal(mi_random_is_initialized(ctx));
|
||||
mi_assert_internal(ctx != ctx_new);
|
||||
chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new);
|
||||
const uintptr_t nonce_rnd = _mi_random_next(ctx);
|
||||
chacha_split(ctx, (uintptr_t)ctx_new ^ nonce_rnd /*nonce*/, ctx_new);
|
||||
}
|
||||
|
||||
uintptr_t _mi_random_next(mi_random_ctx_t* ctx) {
|
||||
mi_assert_internal(mi_random_is_initialized(ctx));
|
||||
#if MI_INTPTR_SIZE <= 4
|
||||
return chacha_next32(ctx);
|
||||
#elif MI_INTPTR_SIZE == 8
|
||||
return (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
|
||||
#else
|
||||
# error "define mi_random_next for this platform"
|
||||
#endif
|
||||
uintptr_t r;
|
||||
do {
|
||||
#if MI_INTPTR_SIZE <= 4
|
||||
r = chacha_next32(ctx);
|
||||
#elif MI_INTPTR_SIZE == 8
|
||||
r = (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx));
|
||||
#else
|
||||
# error "define mi_random_next for this platform"
|
||||
#endif
|
||||
} while (r==0);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +170,7 @@ uintptr_t _mi_os_random_weak(uintptr_t extra_seed) {
|
||||
x ^= _mi_prim_clock_now();
|
||||
// and do a few randomization steps
|
||||
uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1;
|
||||
for (uintptr_t i = 0; i < max; i++) {
|
||||
for (uintptr_t i = 0; i < max || x==0; i++, x++) {
|
||||
x = _mi_random_shuffle(x);
|
||||
}
|
||||
mi_assert_internal(x != 0);
|
||||
@@ -179,9 +186,12 @@ static void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) {
|
||||
if (!use_weak) { _mi_warning_message("unable to use secure randomness\n"); }
|
||||
#endif
|
||||
uintptr_t x = _mi_os_random_weak(0);
|
||||
for (size_t i = 0; i < 8; i++) { // key is eight 32-bit words.
|
||||
for (size_t i = 0; i < 32; i+=4, x++) {
|
||||
x = _mi_random_shuffle(x);
|
||||
((uint32_t*)key)[i] = (uint32_t)x;
|
||||
key[i] = (uint8_t)(x);
|
||||
key[i+1] = (uint8_t)(x>>8);
|
||||
key[i+2] = (uint8_t)(x>>16);
|
||||
key[i+3] = (uint8_t)(x>>24);
|
||||
}
|
||||
ctx->weak = true;
|
||||
}
|
||||
|
||||
@@ -16,138 +16,132 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include "mimalloc/internal.h"
|
||||
#include "mimalloc/atomic.h"
|
||||
|
||||
#if (MI_INTPTR_SIZE==8)
|
||||
#define MI_MAX_ADDRESS ((size_t)40 << 40) // 40TB (to include huge page areas)
|
||||
// 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_MAX_ADDRESS ((size_t)2 << 30) // 2Gb
|
||||
#define MI_SEGMENT_MAP_MAX_ADDRESS (UINT32_MAX)
|
||||
#endif
|
||||
|
||||
#define MI_SEGMENT_MAP_BITS (MI_MAX_ADDRESS / MI_SEGMENT_SIZE)
|
||||
#define MI_SEGMENT_MAP_SIZE (MI_SEGMENT_MAP_BITS / 8)
|
||||
#define MI_SEGMENT_MAP_WSIZE (MI_SEGMENT_MAP_SIZE / MI_INTPTR_SIZE)
|
||||
#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
|
||||
|
||||
static _Atomic(uintptr_t) mi_segment_map[MI_SEGMENT_MAP_WSIZE + 1]; // 2KiB per TB with 64MiB segments
|
||||
#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
|
||||
|
||||
static size_t mi_segment_map_index_of(const mi_segment_t* segment, size_t* bitidx) {
|
||||
#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?
|
||||
if ((uintptr_t)segment >= MI_MAX_ADDRESS) {
|
||||
*bitidx = 0;
|
||||
return MI_SEGMENT_MAP_WSIZE;
|
||||
}
|
||||
else {
|
||||
const uintptr_t segindex = ((uintptr_t)segment) / MI_SEGMENT_SIZE;
|
||||
*bitidx = segindex % MI_INTPTR_BITS;
|
||||
const size_t mapindex = segindex / MI_INTPTR_BITS;
|
||||
mi_assert_internal(mapindex < MI_SEGMENT_MAP_WSIZE);
|
||||
return mapindex;
|
||||
*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;
|
||||
size_t index = mi_segment_map_index_of(segment, &bitidx);
|
||||
mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE);
|
||||
if (index==MI_SEGMENT_MAP_WSIZE) return;
|
||||
uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
|
||||
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(&mi_segment_map[index], &mask, newmask));
|
||||
} 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;
|
||||
size_t index = mi_segment_map_index_of(segment, &bitidx);
|
||||
mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE);
|
||||
if (index == MI_SEGMENT_MAP_WSIZE) return;
|
||||
uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
|
||||
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(&mi_segment_map[index], &mask, newmask));
|
||||
} 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);
|
||||
mi_assert_internal(segment != NULL);
|
||||
mi_segment_t* segment = _mi_ptr_segment(p); // segment can be NULL
|
||||
size_t index;
|
||||
size_t bitidx;
|
||||
size_t index = mi_segment_map_index_of(segment, &bitidx);
|
||||
// fast path: for any pointer to valid small/medium/large object or first MI_SEGMENT_SIZE in huge
|
||||
const uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]);
|
||||
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) {
|
||||
return segment; // yes, allocated by us
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
if (index==MI_SEGMENT_MAP_WSIZE) return NULL;
|
||||
|
||||
// TODO: maintain max/min allocated range for efficiency for more efficient rejection of invalid pointers?
|
||||
|
||||
// search downwards for the first segment in case it is an interior pointer
|
||||
// could be slow but searches in MI_INTPTR_SIZE * MI_SEGMENT_SIZE (512MiB) steps through
|
||||
// valid huge objects
|
||||
// note: we could maintain a lowest index to speed up the path for invalid pointers?
|
||||
size_t lobitidx;
|
||||
size_t loindex;
|
||||
uintptr_t lobits = mask & (((uintptr_t)1 << bitidx) - 1);
|
||||
if (lobits != 0) {
|
||||
loindex = index;
|
||||
lobitidx = mi_bsr(lobits); // lobits != 0
|
||||
}
|
||||
else if (index == 0) {
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
mi_assert_internal(index > 0);
|
||||
uintptr_t lomask = mask;
|
||||
loindex = index;
|
||||
do {
|
||||
loindex--;
|
||||
lomask = mi_atomic_load_relaxed(&mi_segment_map[loindex]);
|
||||
} while (lomask != 0 && loindex > 0);
|
||||
if (lomask == 0) return NULL;
|
||||
lobitidx = mi_bsr(lomask); // lomask != 0
|
||||
}
|
||||
mi_assert_internal(loindex < MI_SEGMENT_MAP_WSIZE);
|
||||
// take difference as the addresses could be larger than the MAX_ADDRESS space.
|
||||
size_t diff = (((index - loindex) * (8*MI_INTPTR_SIZE)) + bitidx - lobitidx) * MI_SEGMENT_SIZE;
|
||||
segment = (mi_segment_t*)((uint8_t*)segment - diff);
|
||||
|
||||
if (segment == NULL) return NULL;
|
||||
mi_assert_internal((void*)segment < p);
|
||||
bool cookie_ok = (_mi_ptr_cookie(segment) == segment->cookie);
|
||||
mi_assert_internal(cookie_ok);
|
||||
if mi_unlikely(!cookie_ok) return NULL;
|
||||
if (((uint8_t*)segment + mi_segment_size(segment)) <= (uint8_t*)p) return NULL; // outside the range
|
||||
mi_assert_internal(p >= (void*)segment && (uint8_t*)p < (uint8_t*)segment + mi_segment_size(segment));
|
||||
return segment;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Is this a valid pointer in our heap?
|
||||
static bool mi_is_valid_pointer(const void* p) {
|
||||
return ((_mi_segment_of(p) != NULL) || (_mi_arena_contains(p)));
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
// Return the full segment range belonging to a pointer
|
||||
static void* mi_segment_range_of(const void* p, size_t* size) {
|
||||
mi_segment_t* segment = _mi_segment_of(p);
|
||||
if (segment == NULL) {
|
||||
if (size != NULL) *size = 0;
|
||||
return NULL;
|
||||
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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (size != NULL) *size = segment->segment_size;
|
||||
return segment;
|
||||
}
|
||||
mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld));
|
||||
mi_assert_internal(page == NULL || (mi_segment_page_size(_mi_page_segment(page)) - (MI_SECURE == 0 ? 0 : _mi_os_page_size())) >= block_size);
|
||||
mi_reset_delayed(tld);
|
||||
mi_assert_internal(page == NULL || mi_page_not_in_queue(page, tld));
|
||||
return page;
|
||||
}
|
||||
*/
|
||||
+409
-359
File diff suppressed because it is too large.
Load diff
@@ -27,6 +27,7 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include "bitmap.c"
|
||||
#include "heap.c"
|
||||
#include "init.c"
|
||||
#include "libc.c"
|
||||
#include "options.c"
|
||||
#include "os.c"
|
||||
#include "page.c" // includes page-queue.c
|
||||
|
||||
+315
-135
@@ -9,7 +9,6 @@ terms of the MIT license. A copy of the license can be found in the file
|
||||
#include "mimalloc/atomic.h"
|
||||
#include "mimalloc/prim.h"
|
||||
|
||||
#include <stdio.h> // snprintf
|
||||
#include <string.h> // memset
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1920)
|
||||
@@ -27,38 +26,29 @@ static bool mi_is_in_main(void* stat) {
|
||||
|
||||
static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) {
|
||||
if (amount == 0) return;
|
||||
if (mi_is_in_main(stat))
|
||||
if mi_unlikely(mi_is_in_main(stat))
|
||||
{
|
||||
// add atomically (for abandoned pages)
|
||||
int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount);
|
||||
// if (stat == &_mi_stats_main.committed) { mi_assert_internal(current + amount >= 0); };
|
||||
mi_atomic_maxi64_relaxed(&stat->peak, current + amount);
|
||||
if (amount > 0) {
|
||||
mi_atomic_addi64_relaxed(&stat->allocated,amount);
|
||||
}
|
||||
else {
|
||||
mi_atomic_addi64_relaxed(&stat->freed, -amount);
|
||||
mi_atomic_addi64_relaxed(&stat->total,amount);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// add thread local
|
||||
stat->current += amount;
|
||||
if (stat->current > stat->peak) stat->peak = stat->current;
|
||||
if (amount > 0) {
|
||||
stat->allocated += amount;
|
||||
}
|
||||
else {
|
||||
stat->freed += -amount;
|
||||
}
|
||||
if (stat->current > stat->peak) { stat->peak = stat->current; }
|
||||
if (amount > 0) { stat->total += amount; }
|
||||
}
|
||||
}
|
||||
|
||||
void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) {
|
||||
if (mi_is_in_main(stat)) {
|
||||
mi_atomic_addi64_relaxed( &stat->count, 1 );
|
||||
mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount );
|
||||
}
|
||||
else {
|
||||
stat->count++;
|
||||
stat->total += amount;
|
||||
}
|
||||
}
|
||||
@@ -71,64 +61,70 @@ void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) {
|
||||
mi_stat_update(stat, -((int64_t)amount));
|
||||
}
|
||||
|
||||
// must be thread safe as it is called from stats_merge
|
||||
static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) {
|
||||
if (stat==src) return;
|
||||
if (src->allocated==0 && src->freed==0) return;
|
||||
mi_atomic_addi64_relaxed( &stat->allocated, src->allocated * unit);
|
||||
mi_atomic_addi64_relaxed( &stat->current, src->current * unit);
|
||||
mi_atomic_addi64_relaxed( &stat->freed, src->freed * unit);
|
||||
// peak scores do not work across threads..
|
||||
mi_atomic_addi64_relaxed( &stat->peak, src->peak * unit);
|
||||
|
||||
static void mi_stat_adjust(mi_stat_count_t* stat, int64_t amount) {
|
||||
if (amount == 0) return;
|
||||
if mi_unlikely(mi_is_in_main(stat))
|
||||
{
|
||||
// adjust atomically
|
||||
mi_atomic_addi64_relaxed(&stat->current, amount);
|
||||
mi_atomic_addi64_relaxed(&stat->total,amount);
|
||||
}
|
||||
else {
|
||||
// adjust local
|
||||
stat->current += amount;
|
||||
stat->total += amount;
|
||||
}
|
||||
}
|
||||
|
||||
static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t* src, int64_t unit) {
|
||||
if (stat==src) return;
|
||||
mi_atomic_addi64_relaxed( &stat->total, src->total * unit);
|
||||
mi_atomic_addi64_relaxed( &stat->count, src->count * unit);
|
||||
void _mi_stat_adjust_decrease(mi_stat_count_t* stat, size_t amount) {
|
||||
mi_stat_adjust(stat, -((int64_t)amount));
|
||||
}
|
||||
|
||||
|
||||
// must be thread safe as it is called from stats_merge
|
||||
static void mi_stat_count_add_mt(mi_stat_count_t* stat, const mi_stat_count_t* src) {
|
||||
if (stat==src) return;
|
||||
mi_atomic_void_addi64_relaxed(&stat->total, &src->total);
|
||||
const int64_t prev_current = mi_atomic_addi64_relaxed(&stat->current, src->current);
|
||||
|
||||
// Global current plus thread peak approximates new global peak
|
||||
// note: peak scores do really not work across threads.
|
||||
// we used to just add them together but that often overestimates in practice.
|
||||
// similarly, max does not seem to work well. The current approach
|
||||
// by Artem Kharytoniuk (@artem-lunarg) seems to work better, see PR#1112
|
||||
// for a longer description.
|
||||
mi_atomic_maxi64_relaxed(&stat->peak, prev_current + src->peak);
|
||||
}
|
||||
|
||||
static void mi_stat_counter_add_mt(mi_stat_counter_t* stat, const mi_stat_counter_t* src) {
|
||||
if (stat==src) return;
|
||||
mi_atomic_void_addi64_relaxed(&stat->total, &src->total);
|
||||
}
|
||||
|
||||
#define MI_STAT_COUNT(stat) mi_stat_count_add_mt(&stats->stat, &src->stat);
|
||||
#define MI_STAT_COUNTER(stat) mi_stat_counter_add_mt(&stats->stat, &src->stat);
|
||||
|
||||
// must be thread safe as it is called from stats_merge
|
||||
static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
|
||||
if (stats==src) return;
|
||||
mi_stat_add(&stats->segments, &src->segments,1);
|
||||
mi_stat_add(&stats->pages, &src->pages,1);
|
||||
mi_stat_add(&stats->reserved, &src->reserved, 1);
|
||||
mi_stat_add(&stats->committed, &src->committed, 1);
|
||||
mi_stat_add(&stats->reset, &src->reset, 1);
|
||||
mi_stat_add(&stats->purged, &src->purged, 1);
|
||||
mi_stat_add(&stats->page_committed, &src->page_committed, 1);
|
||||
|
||||
mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1);
|
||||
mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1);
|
||||
mi_stat_add(&stats->threads, &src->threads, 1);
|
||||
// copy all fields
|
||||
MI_STAT_FIELDS()
|
||||
|
||||
mi_stat_add(&stats->malloc, &src->malloc, 1);
|
||||
mi_stat_add(&stats->segments_cache, &src->segments_cache, 1);
|
||||
mi_stat_add(&stats->normal, &src->normal, 1);
|
||||
mi_stat_add(&stats->huge, &src->huge, 1);
|
||||
mi_stat_add(&stats->large, &src->large, 1);
|
||||
|
||||
mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1);
|
||||
mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1);
|
||||
mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1);
|
||||
mi_stat_counter_add(&stats->reset_calls, &src->reset_calls, 1);
|
||||
mi_stat_counter_add(&stats->purge_calls, &src->purge_calls, 1);
|
||||
|
||||
mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1);
|
||||
mi_stat_counter_add(&stats->searches, &src->searches, 1);
|
||||
mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1);
|
||||
mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1);
|
||||
mi_stat_counter_add(&stats->large_count, &src->large_count, 1);
|
||||
#if MI_STAT>1
|
||||
#if MI_STAT>1
|
||||
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
|
||||
if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) {
|
||||
mi_stat_add(&stats->normal_bins[i], &src->normal_bins[i], 1);
|
||||
}
|
||||
mi_stat_count_add_mt(&stats->malloc_bins[i], &src->malloc_bins[i]);
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
|
||||
mi_stat_count_add_mt(&stats->page_bins[i], &src->page_bins[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef MI_STAT_COUNT
|
||||
#undef MI_STAT_COUNTER
|
||||
|
||||
/* -----------------------------------------------------------
|
||||
Display statistics
|
||||
----------------------------------------------------------- */
|
||||
@@ -136,7 +132,7 @@ static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) {
|
||||
// unit > 0 : size in binary bytes
|
||||
// unit == 0: count as decimal
|
||||
// unit < 0 : count in binary
|
||||
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) {
|
||||
static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, bool limitwidth) {
|
||||
char buf[32]; buf[0] = 0;
|
||||
int len = 32;
|
||||
const char* suffix = (unit <= 0 ? " " : "B");
|
||||
@@ -146,7 +142,7 @@ static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void*
|
||||
const int64_t pos = (n < 0 ? -n : n);
|
||||
if (pos < base) {
|
||||
if (n!=1 || suffix[0] != 'B') { // skip printing 1 B for the unit column
|
||||
snprintf(buf, len, "%d %-3s", (int)n, (n==0 ? "" : suffix));
|
||||
_mi_snprintf(buf, len, "%lld %-3s", (long long)n, (n==0 ? "" : suffix));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -158,15 +154,20 @@ static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void*
|
||||
const long whole = (long)(tens/10);
|
||||
const long frac1 = (long)(tens%10);
|
||||
char unitdesc[8];
|
||||
snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);
|
||||
snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);
|
||||
_mi_snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix);
|
||||
_mi_snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc);
|
||||
}
|
||||
if (limitwidth) {
|
||||
_mi_fprintf(out, arg, "%12s", buf);
|
||||
}
|
||||
else {
|
||||
_mi_fprintf(out, arg, "%s", buf);
|
||||
}
|
||||
_mi_fprintf(out, arg, (fmt==NULL ? "%12s" : fmt), buf);
|
||||
}
|
||||
|
||||
|
||||
static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
|
||||
mi_printf_amount(n,unit,out,arg,NULL);
|
||||
mi_printf_amount(n,unit,out,arg,true);
|
||||
}
|
||||
|
||||
static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) {
|
||||
@@ -176,44 +177,41 @@ static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* ar
|
||||
|
||||
static void mi_stat_print_ex(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg, const char* notok ) {
|
||||
_mi_fprintf(out, arg,"%10s:", msg);
|
||||
if (unit > 0) {
|
||||
mi_print_amount(stat->peak, unit, out, arg);
|
||||
mi_print_amount(stat->allocated, unit, out, arg);
|
||||
mi_print_amount(stat->freed, unit, out, arg);
|
||||
mi_print_amount(stat->current, unit, out, arg);
|
||||
mi_print_amount(unit, 1, out, arg);
|
||||
mi_print_count(stat->allocated, unit, out, arg);
|
||||
if (stat->allocated > stat->freed) {
|
||||
if (unit != 0) {
|
||||
if (unit > 0) {
|
||||
mi_print_amount(stat->peak, unit, out, arg);
|
||||
mi_print_amount(stat->total, unit, out, arg);
|
||||
// mi_print_amount(stat->freed, unit, out, arg);
|
||||
mi_print_amount(stat->current, unit, out, arg);
|
||||
mi_print_amount(unit, 1, out, arg);
|
||||
mi_print_count(stat->total, unit, out, arg);
|
||||
}
|
||||
else {
|
||||
mi_print_amount(stat->peak, -1, out, arg);
|
||||
mi_print_amount(stat->total, -1, out, arg);
|
||||
// mi_print_amount(stat->freed, -1, out, arg);
|
||||
mi_print_amount(stat->current, -1, out, arg);
|
||||
if (unit == -1) {
|
||||
_mi_fprintf(out, arg, "%24s", "");
|
||||
}
|
||||
else {
|
||||
mi_print_amount(-unit, 1, out, arg);
|
||||
mi_print_count((stat->total / -unit), 0, out, arg);
|
||||
}
|
||||
}
|
||||
if (stat->current != 0) {
|
||||
_mi_fprintf(out, arg, " ");
|
||||
_mi_fprintf(out, arg, (notok == NULL ? "not all freed" : notok));
|
||||
_mi_fprintf(out, arg, "%s", (notok == NULL ? "not all freed" : notok));
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
else {
|
||||
_mi_fprintf(out, arg, " ok\n");
|
||||
}
|
||||
}
|
||||
else if (unit<0) {
|
||||
mi_print_amount(stat->peak, -1, out, arg);
|
||||
mi_print_amount(stat->allocated, -1, out, arg);
|
||||
mi_print_amount(stat->freed, -1, out, arg);
|
||||
mi_print_amount(stat->current, -1, out, arg);
|
||||
if (unit==-1) {
|
||||
_mi_fprintf(out, arg, "%24s", "");
|
||||
}
|
||||
else {
|
||||
mi_print_amount(-unit, 1, out, arg);
|
||||
mi_print_count((stat->allocated / -unit), 0, out, arg);
|
||||
}
|
||||
if (stat->allocated > stat->freed)
|
||||
_mi_fprintf(out, arg, " not all freed!\n");
|
||||
else
|
||||
_mi_fprintf(out, arg, " ok\n");
|
||||
}
|
||||
else {
|
||||
mi_print_amount(stat->peak, 1, out, arg);
|
||||
mi_print_amount(stat->allocated, 1, out, arg);
|
||||
_mi_fprintf(out, arg, "%11s", " "); // no freed
|
||||
mi_print_amount(stat->current, 1, out, arg);
|
||||
mi_print_amount(stat->peak, 0, out, arg);
|
||||
mi_print_amount(stat->total, 0, out, arg);
|
||||
mi_print_amount(stat->current, 0, out, arg);
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
}
|
||||
@@ -222,29 +220,37 @@ static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t
|
||||
mi_stat_print_ex(stat, msg, unit, out, arg, NULL);
|
||||
}
|
||||
|
||||
static void mi_stat_peak_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
|
||||
#if MI_STAT>1
|
||||
static void mi_stat_total_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) {
|
||||
_mi_fprintf(out, arg, "%10s:", msg);
|
||||
mi_print_amount(stat->peak, unit, out, arg);
|
||||
_mi_fprintf(out, arg, "%12s", " "); // no peak
|
||||
mi_print_amount(stat->total, unit, out, arg);
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
|
||||
_mi_fprintf(out, arg, "%10s:", msg);
|
||||
mi_print_amount(stat->total, -1, out, arg);
|
||||
mi_print_amount(stat->total, 0, out, arg);
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
|
||||
static void mi_stat_counter_print_size(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) {
|
||||
_mi_fprintf(out, arg, "%10s:", msg);
|
||||
mi_print_amount(stat->total, 1, out, arg);
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
|
||||
static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) {
|
||||
const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count));
|
||||
const long avg_whole = (long)(avg_tens/10);
|
||||
const long avg_frac1 = (long)(avg_tens%10);
|
||||
_mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n", msg, avg_whole, avg_frac1);
|
||||
static void mi_stat_average_print(int64_t count, int64_t total, const char* msg, mi_output_fun* out, void* arg) {
|
||||
const int64_t avg_tens = (count == 0 ? 0 : (total*10 / count));
|
||||
const int64_t avg_whole = avg_tens/10;
|
||||
const int64_t avg_frac1 = avg_tens%10;
|
||||
_mi_fprintf(out, arg, "%10s: %5lld.%lld avg\n", msg, avg_whole, avg_frac1);
|
||||
}
|
||||
|
||||
|
||||
static void mi_print_header(mi_output_fun* out, void* arg ) {
|
||||
_mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s %11s\n", "heap stats", "peak ", "total ", "freed ", "current ", "unit ", "count ");
|
||||
_mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s\n", "heap stats", "peak ", "total ", "current ", "block ", "total# ");
|
||||
}
|
||||
|
||||
#if MI_STAT>1
|
||||
@@ -252,10 +258,10 @@ static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const c
|
||||
bool found = false;
|
||||
char buf[64];
|
||||
for (size_t i = 0; i <= max; i++) {
|
||||
if (bins[i].allocated > 0) {
|
||||
if (bins[i].total > 0) {
|
||||
found = true;
|
||||
int64_t unit = _mi_bin_size((uint8_t)i);
|
||||
snprintf(buf, 64, "%s %3lu", fmt, (long)i);
|
||||
_mi_snprintf(buf, 64, "%s %3lu", fmt, (long)i);
|
||||
mi_stat_print(&bins[i], buf, unit, out, arg);
|
||||
}
|
||||
}
|
||||
@@ -313,41 +319,44 @@ static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0)
|
||||
// and print using that
|
||||
mi_print_header(out,arg);
|
||||
#if MI_STAT>1
|
||||
mi_stats_print_bins(stats->normal_bins, MI_BIN_HUGE, "normal",out,arg);
|
||||
mi_stats_print_bins(stats->malloc_bins, MI_BIN_HUGE, "bin",out,arg);
|
||||
#endif
|
||||
#if MI_STAT
|
||||
mi_stat_print(&stats->normal, "normal", (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg);
|
||||
mi_stat_print(&stats->large, "large", (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg);
|
||||
mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg);
|
||||
mi_stat_count_t total = { 0,0,0,0 };
|
||||
mi_stat_add(&total, &stats->normal, 1);
|
||||
mi_stat_add(&total, &stats->large, 1);
|
||||
mi_stat_add(&total, &stats->huge, 1);
|
||||
mi_stat_print(&total, "total", 1, out, arg);
|
||||
mi_stat_print(&stats->malloc_normal, "binned", (stats->malloc_normal_count.total == 0 ? -1 : 1), out, arg);
|
||||
mi_stat_print(&stats->malloc_huge, "huge", (stats->malloc_huge_count.total == 0 ? -1 : 1), out, arg);
|
||||
mi_stat_count_t total = { 0,0,0 };
|
||||
mi_stat_count_add_mt(&total, &stats->malloc_normal);
|
||||
// mi_stat_count_add(&total, &stats->malloc_large);
|
||||
mi_stat_count_add_mt(&total, &stats->malloc_huge);
|
||||
mi_stat_print_ex(&total, "total", 1, out, arg, "");
|
||||
#endif
|
||||
#if MI_STAT>1
|
||||
mi_stat_print(&stats->malloc, "malloc req", 1, out, arg);
|
||||
mi_stat_total_print(&stats->malloc_requested, "malloc req", 1, out, arg);
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
#endif
|
||||
mi_stat_print_ex(&stats->reserved, "reserved", 1, out, arg, "");
|
||||
mi_stat_print_ex(&stats->committed, "committed", 1, out, arg, "");
|
||||
mi_stat_peak_print(&stats->reset, "reset", 1, out, arg );
|
||||
mi_stat_peak_print(&stats->purged, "purged", 1, out, arg );
|
||||
mi_stat_print(&stats->page_committed, "touched", 1, out, arg);
|
||||
mi_stat_print(&stats->segments, "segments", -1, out, arg);
|
||||
mi_stat_print(&stats->segments_abandoned, "-abandoned", -1, out, arg);
|
||||
mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg);
|
||||
mi_stat_print(&stats->pages, "pages", -1, out, arg);
|
||||
mi_stat_print(&stats->pages_abandoned, "-abandoned", -1, out, arg);
|
||||
mi_stat_counter_print_size(&stats->reset, "reset", out, arg );
|
||||
mi_stat_counter_print_size(&stats->purged, "purged", out, arg );
|
||||
mi_stat_print_ex(&stats->page_committed, "touched", 1, out, arg, "");
|
||||
mi_stat_print(&stats->segments, "segments", 0, out, arg);
|
||||
mi_stat_print(&stats->segments_abandoned, "-abandoned", 0, out, arg);
|
||||
mi_stat_print(&stats->segments_cache, "-cached", 0, out, arg);
|
||||
mi_stat_print(&stats->pages, "pages", 0, out, arg);
|
||||
mi_stat_print(&stats->pages_abandoned, "-abandoned", 0, out, arg);
|
||||
mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg);
|
||||
mi_stat_counter_print(&stats->page_no_retire, "-noretire", out, arg);
|
||||
mi_stat_counter_print(&stats->pages_retire, "-retire", out, arg);
|
||||
mi_stat_counter_print(&stats->arena_count, "arenas", out, arg);
|
||||
// mi_stat_counter_print(&stats->arena_crossover_count, "-crossover", out, arg);
|
||||
mi_stat_counter_print(&stats->arena_rollback_count, "-rollback", out, arg);
|
||||
mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg);
|
||||
mi_stat_counter_print(&stats->commit_calls, "commits", out, arg);
|
||||
mi_stat_counter_print(&stats->reset_calls, "resets", out, arg);
|
||||
mi_stat_counter_print(&stats->purge_calls, "purges", out, arg);
|
||||
mi_stat_print(&stats->threads, "threads", -1, out, arg);
|
||||
mi_stat_counter_print_avg(&stats->searches, "searches", out, arg);
|
||||
_mi_fprintf(out, arg, "%10s: %5zu\n", "numa nodes", _mi_os_numa_node_count());
|
||||
mi_stat_counter_print(&stats->malloc_guarded_count, "guarded", out, arg);
|
||||
mi_stat_print(&stats->threads, "threads", 0, out, arg);
|
||||
mi_stat_average_print(stats->page_searches_count.total, stats->page_searches.total, "searches", out, arg);
|
||||
_mi_fprintf(out, arg, "%10s: %5i\n", "numa nodes", _mi_os_numa_node_count());
|
||||
|
||||
size_t elapsed;
|
||||
size_t user_time;
|
||||
@@ -358,13 +367,13 @@ static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0)
|
||||
size_t peak_commit;
|
||||
size_t page_faults;
|
||||
mi_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults);
|
||||
_mi_fprintf(out, arg, "%10s: %5ld.%03ld s\n", "elapsed", elapsed/1000, elapsed%1000);
|
||||
_mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, rss: ", "process",
|
||||
user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults );
|
||||
mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s");
|
||||
_mi_fprintf(out, arg, "%10s: %5zu.%03zu s\n", "elapsed", elapsed/1000, elapsed%1000);
|
||||
_mi_fprintf(out, arg, "%10s: user: %zu.%03zu s, system: %zu.%03zu s, faults: %zu, peak rss: ", "process",
|
||||
user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, page_faults );
|
||||
mi_printf_amount((int64_t)peak_rss, 1, out, arg, false);
|
||||
if (peak_commit > 0) {
|
||||
_mi_fprintf(out, arg, ", commit: ");
|
||||
mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s");
|
||||
_mi_fprintf(out, arg, ", peak commit: ");
|
||||
mi_printf_amount((int64_t)peak_commit, 1, out, arg, false);
|
||||
}
|
||||
_mi_fprintf(out, arg, "\n");
|
||||
}
|
||||
@@ -394,6 +403,10 @@ void mi_stats_merge(void) mi_attr_noexcept {
|
||||
mi_stats_merge_from( mi_stats_get_default() );
|
||||
}
|
||||
|
||||
void _mi_stats_merge_thread(mi_tld_t* tld) {
|
||||
mi_stats_merge_from( &tld->stats );
|
||||
}
|
||||
|
||||
void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done`
|
||||
mi_stats_merge_from(stats);
|
||||
}
|
||||
@@ -465,3 +478,170 @@ mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, s
|
||||
if (peak_commit!=NULL) *peak_commit = pinfo.peak_commit;
|
||||
if (page_faults!=NULL) *page_faults = pinfo.page_faults;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Return statistics
|
||||
// --------------------------------------------------------
|
||||
|
||||
bool mi_stats_get(mi_stats_t* stats) mi_attr_noexcept {
|
||||
if (stats == NULL || stats->size != sizeof(mi_stats_t) || stats->version != MI_STAT_VERSION) return false;
|
||||
_mi_memzero(stats,stats->size);
|
||||
_mi_memcpy(stats, &_mi_stats_main, sizeof(mi_stats_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Statics in json format
|
||||
// --------------------------------------------------------
|
||||
|
||||
typedef struct mi_heap_buf_s {
|
||||
char* buf;
|
||||
size_t size;
|
||||
size_t used;
|
||||
bool can_realloc;
|
||||
} mi_heap_buf_t;
|
||||
|
||||
static bool mi_heap_buf_expand(mi_heap_buf_t* hbuf) {
|
||||
if (hbuf==NULL) return false;
|
||||
if (hbuf->buf != NULL && hbuf->size>0) {
|
||||
hbuf->buf[hbuf->size-1] = 0;
|
||||
}
|
||||
if (hbuf->size > SIZE_MAX/2 || !hbuf->can_realloc) return false;
|
||||
const size_t newsize = (hbuf->size == 0 ? mi_good_size(12*MI_KiB) : 2*hbuf->size);
|
||||
char* const newbuf = (char*)mi_rezalloc(hbuf->buf, newsize);
|
||||
if (newbuf == NULL) return false;
|
||||
hbuf->buf = newbuf;
|
||||
hbuf->size = newsize;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print(mi_heap_buf_t* hbuf, const char* msg) {
|
||||
if (msg==NULL || hbuf==NULL) return;
|
||||
if (hbuf->used + 1 >= hbuf->size && !hbuf->can_realloc) return;
|
||||
for (const char* src = msg; *src != 0; src++) {
|
||||
char c = *src;
|
||||
if (hbuf->used + 1 >= hbuf->size) {
|
||||
if (!mi_heap_buf_expand(hbuf)) return;
|
||||
}
|
||||
mi_assert_internal(hbuf->used < hbuf->size);
|
||||
hbuf->buf[hbuf->used++] = c;
|
||||
}
|
||||
mi_assert_internal(hbuf->used < hbuf->size);
|
||||
hbuf->buf[hbuf->used] = 0;
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_count_bin(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, size_t bin, bool add_comma) {
|
||||
const size_t binsize = _mi_bin_size(bin);
|
||||
const size_t pagesize = (binsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_SMALL_PAGE_SIZE :
|
||||
(binsize <= MI_MEDIUM_OBJ_SIZE_MAX ? MI_MEDIUM_PAGE_SIZE :
|
||||
#if MI_LARGE_PAGE_SIZE
|
||||
(binsize <= MI_LARGE_OBJ_SIZE_MAX ? MI_LARGE_PAGE_SIZE : 0)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
));
|
||||
char buf[128];
|
||||
_mi_snprintf(buf, 128, "%s{ \"total\": %lld, \"peak\": %lld, \"current\": %lld, \"block_size\": %zu, \"page_size\": %zu }%s\n", prefix, stat->total, stat->peak, stat->current, binsize, pagesize, (add_comma ? "," : ""));
|
||||
buf[127] = 0;
|
||||
mi_heap_buf_print(hbuf, buf);
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_count(mi_heap_buf_t* hbuf, const char* prefix, mi_stat_count_t* stat, bool add_comma) {
|
||||
char buf[128];
|
||||
_mi_snprintf(buf, 128, "%s{ \"total\": %lld, \"peak\": %lld, \"current\": %lld }%s\n", prefix, stat->total, stat->peak, stat->current, (add_comma ? "," : ""));
|
||||
buf[127] = 0;
|
||||
mi_heap_buf_print(hbuf, buf);
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_count_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_count_t* stat) {
|
||||
char buf[128];
|
||||
_mi_snprintf(buf, 128, " \"%s\": ", name);
|
||||
buf[127] = 0;
|
||||
mi_heap_buf_print(hbuf, buf);
|
||||
mi_heap_buf_print_count(hbuf, "", stat, true);
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_value(mi_heap_buf_t* hbuf, const char* name, int64_t val) {
|
||||
char buf[128];
|
||||
_mi_snprintf(buf, 128, " \"%s\": %lld,\n", name, val);
|
||||
buf[127] = 0;
|
||||
mi_heap_buf_print(hbuf, buf);
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_size(mi_heap_buf_t* hbuf, const char* name, size_t val, bool add_comma) {
|
||||
char buf[128];
|
||||
_mi_snprintf(buf, 128, " \"%s\": %zu%s\n", name, val, (add_comma ? "," : ""));
|
||||
buf[127] = 0;
|
||||
mi_heap_buf_print(hbuf, buf);
|
||||
}
|
||||
|
||||
static void mi_heap_buf_print_counter_value(mi_heap_buf_t* hbuf, const char* name, mi_stat_counter_t* stat) {
|
||||
mi_heap_buf_print_value(hbuf, name, stat->total);
|
||||
}
|
||||
|
||||
#define MI_STAT_COUNT(stat) mi_heap_buf_print_count_value(&hbuf, #stat, &stats->stat);
|
||||
#define MI_STAT_COUNTER(stat) mi_heap_buf_print_counter_value(&hbuf, #stat, &stats->stat);
|
||||
|
||||
char* mi_stats_get_json(size_t output_size, char* output_buf) mi_attr_noexcept {
|
||||
mi_heap_buf_t hbuf = { NULL, 0, 0, true };
|
||||
if (output_size > 0 && output_buf != NULL) {
|
||||
_mi_memzero(output_buf, output_size);
|
||||
hbuf.buf = output_buf;
|
||||
hbuf.size = output_size;
|
||||
hbuf.can_realloc = false;
|
||||
}
|
||||
else {
|
||||
if (!mi_heap_buf_expand(&hbuf)) return NULL;
|
||||
}
|
||||
mi_heap_buf_print(&hbuf, "{\n");
|
||||
mi_heap_buf_print_value(&hbuf, "stat_version", MI_STAT_VERSION);
|
||||
mi_heap_buf_print_value(&hbuf, "mimalloc_version", MI_MALLOC_VERSION);
|
||||
|
||||
// process info
|
||||
mi_heap_buf_print(&hbuf, " \"process\": {\n");
|
||||
size_t elapsed;
|
||||
size_t user_time;
|
||||
size_t sys_time;
|
||||
size_t current_rss;
|
||||
size_t peak_rss;
|
||||
size_t current_commit;
|
||||
size_t peak_commit;
|
||||
size_t page_faults;
|
||||
mi_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults);
|
||||
mi_heap_buf_print_size(&hbuf, "elapsed_msecs", elapsed, true);
|
||||
mi_heap_buf_print_size(&hbuf, "user_msecs", user_time, true);
|
||||
mi_heap_buf_print_size(&hbuf, "system_msecs", sys_time, true);
|
||||
mi_heap_buf_print_size(&hbuf, "page_faults", page_faults, true);
|
||||
mi_heap_buf_print_size(&hbuf, "rss_current", current_rss, true);
|
||||
mi_heap_buf_print_size(&hbuf, "rss_peak", peak_rss, true);
|
||||
mi_heap_buf_print_size(&hbuf, "commit_current", current_commit, true);
|
||||
mi_heap_buf_print_size(&hbuf, "commit_peak", peak_commit, false);
|
||||
mi_heap_buf_print(&hbuf, " },\n");
|
||||
|
||||
// statistics
|
||||
mi_stats_t* stats = &_mi_stats_main;
|
||||
MI_STAT_FIELDS()
|
||||
|
||||
// size bins
|
||||
mi_heap_buf_print(&hbuf, " \"malloc_bins\": [\n");
|
||||
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
|
||||
mi_heap_buf_print_count_bin(&hbuf, " ", &stats->malloc_bins[i], i, i!=MI_BIN_HUGE);
|
||||
}
|
||||
mi_heap_buf_print(&hbuf, " ],\n");
|
||||
mi_heap_buf_print(&hbuf, " \"page_bins\": [\n");
|
||||
for (size_t i = 0; i <= MI_BIN_HUGE; i++) {
|
||||
mi_heap_buf_print_count_bin(&hbuf, " ", &stats->page_bins[i], i, i!=MI_BIN_HUGE);
|
||||
}
|
||||
mi_heap_buf_print(&hbuf, " ]\n");
|
||||
mi_heap_buf_print(&hbuf, "}\n");
|
||||
if (hbuf.used >= hbuf.size) {
|
||||
// failed
|
||||
if (hbuf.can_realloc) { mi_free(hbuf.buf); }
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
return hbuf.buf;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1827,10 +1827,10 @@ get_mimalloc_allocated_blocks(PyInterpreterState *interp)
|
||||
}
|
||||
}
|
||||
|
||||
mi_abandoned_pool_t *pool = &interp->mimalloc.abandoned_pool;
|
||||
for (uint8_t tag = 0; tag < _Py_MIMALLOC_HEAP_COUNT; tag++) {
|
||||
_mi_abandoned_pool_visit_blocks(pool, tag, false, &count_blocks,
|
||||
&allocated_blocks);
|
||||
mi_subproc_t *subproc = interp->mimalloc.subproc;
|
||||
for (int tag = 0; tag < _Py_MIMALLOC_HEAP_COUNT; tag++) {
|
||||
mi_abandoned_visit_blocks(subproc, tag, false, &count_blocks,
|
||||
&allocated_blocks);
|
||||
}
|
||||
#else
|
||||
// TODO(sgross): this only counts the current thread's blocks.
|
||||
|
||||
@@ -358,6 +358,7 @@
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc\prim.h" />
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc\track.h" />
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc\types.h" />
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc-stats.h" />
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc.h" />
|
||||
<ClInclude Include="..\Include\modsupport.h" />
|
||||
<ClInclude Include="..\Include\moduleobject.h" />
|
||||
|
||||
@@ -942,6 +942,9 @@
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc.h">
|
||||
<Filter>Include\internal\mimalloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc-stats.h">
|
||||
<Filter>Include\internal\mimalloc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\internal\mimalloc\mimalloc\atomic.h">
|
||||
<Filter>Include\internal\mimalloc</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -400,16 +400,17 @@ gc_visit_heaps_lock_held(PyInterpreterState *interp, mi_block_visit_fun *visitor
|
||||
}
|
||||
}
|
||||
|
||||
// visit blocks in the per-interpreter abandoned pool (from dead threads)
|
||||
mi_abandoned_pool_t *pool = &interp->mimalloc.abandoned_pool;
|
||||
// visit blocks in abandoned segments (from dead threads) of the
|
||||
// per-interpreter mimalloc sub-process.
|
||||
mi_subproc_t *subproc = interp->mimalloc.subproc;
|
||||
arg->offset = offset_base;
|
||||
if (!_mi_abandoned_pool_visit_blocks(pool, _Py_MIMALLOC_HEAP_GC, true,
|
||||
visitor, arg)) {
|
||||
if (!mi_abandoned_visit_blocks(subproc, _Py_MIMALLOC_HEAP_GC, true,
|
||||
visitor, arg)) {
|
||||
return -1;
|
||||
}
|
||||
arg->offset = offset_pre;
|
||||
if (!_mi_abandoned_pool_visit_blocks(pool, _Py_MIMALLOC_HEAP_GC_PRE, true,
|
||||
visitor, arg)) {
|
||||
if (!mi_abandoned_visit_blocks(subproc, _Py_MIMALLOC_HEAP_GC_PRE, true,
|
||||
visitor, arg)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
+21
-5
@@ -466,6 +466,16 @@ alloc_interpreter(void)
|
||||
static void
|
||||
free_interpreter(PyInterpreterState *interp)
|
||||
{
|
||||
#ifdef Py_GIL_DISABLED
|
||||
mi_subproc_t *subproc = interp->mimalloc.subproc;
|
||||
if (subproc != NULL) {
|
||||
// Delete the sub-process only when no abandoned segment points to it.
|
||||
if (mi_atomic_load_relaxed(&subproc->abandoned_count) == 0) {
|
||||
mi_subproc_delete(subproc);
|
||||
}
|
||||
interp->mimalloc.subproc = NULL;
|
||||
}
|
||||
#endif
|
||||
#ifdef Py_STATS
|
||||
if (interp->pystats_struct) {
|
||||
PyMem_RawFree(interp->pystats_struct);
|
||||
@@ -573,6 +583,11 @@ init_interpreter(PyInterpreterState *interp,
|
||||
PyConfig_InitPythonConfig(&interp->config);
|
||||
#ifdef Py_GIL_DISABLED
|
||||
_Py_brc_init_state(interp);
|
||||
|
||||
interp->mimalloc.subproc = mi_subproc_new();
|
||||
if (interp->mimalloc.subproc == NULL) {
|
||||
return _PyStatus_NO_MEMORY();
|
||||
}
|
||||
#endif
|
||||
|
||||
llist_init(&interp->mem_free_queue.head);
|
||||
@@ -3337,10 +3352,11 @@ tstate_mimalloc_bind(PyThreadState *tstate)
|
||||
_mi_tld_init(tld, &mts->heaps[_Py_MIMALLOC_HEAP_MEM]);
|
||||
llist_init(&mts->page_list);
|
||||
|
||||
// Exiting threads push any remaining in-use segments to the abandoned
|
||||
// pool to be re-claimed later by other threads. We use per-interpreter
|
||||
// pools to keep Python objects from different interpreters separate.
|
||||
tld->segments.abandoned = &tstate->interp->mimalloc.abandoned_pool;
|
||||
// Exiting threads abandon any remaining in-use segments to be re-claimed
|
||||
// later by other threads. We use a per-interpreter mimalloc sub-process
|
||||
// to keep Python objects from different interpreters separate.
|
||||
assert(tstate->interp->mimalloc.subproc != NULL);
|
||||
tld->segments.subproc = tstate->interp->mimalloc.subproc;
|
||||
|
||||
// Don't fill in the first N bytes up to ob_type in debug builds. We may
|
||||
// access ob_tid and the refcount fields in the dict and list lock-less
|
||||
@@ -3358,7 +3374,7 @@ tstate_mimalloc_bind(PyThreadState *tstate)
|
||||
|
||||
// Initialize each heap
|
||||
for (uint8_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) {
|
||||
_mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none(), false, i);
|
||||
_mi_heap_init(&mts->heaps[i], tld, _mi_arena_id_none(), false, i);
|
||||
mts->heaps[i].debug_offset = (uint8_t)debug_offsets[i];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user