/src/cpython/Objects/mimalloc/alloc.c
Line | Count | Source |
1 | | /* ---------------------------------------------------------------------------- |
2 | | Copyright (c) 2018-2024, Microsoft Research, Daan Leijen |
3 | | This is free software; you can redistribute it and/or modify it under the |
4 | | terms of the MIT license. A copy of the license can be found in the file |
5 | | "LICENSE" at the root of this distribution. |
6 | | -----------------------------------------------------------------------------*/ |
7 | | #ifndef _DEFAULT_SOURCE |
8 | | #define _DEFAULT_SOURCE // for realpath() on Linux |
9 | | #endif |
10 | | |
11 | | #include "mimalloc.h" |
12 | | #include "mimalloc/internal.h" |
13 | | #include "mimalloc/atomic.h" |
14 | | #include "mimalloc/prim.h" // _mi_prim_thread_id() |
15 | | |
16 | | #include <string.h> // memset, strlen (for mi_strdup) |
17 | | #include <stdlib.h> // malloc, abort |
18 | | |
19 | | // CPython: don't export the weak C++ `std::get_new_handler` symbol from libpython |
20 | 0 | #define _ZSt15get_new_handlerv _Py__ZSt15get_new_handlerv |
21 | | |
22 | | #if (MI_DEBUG>0) |
23 | | // CPython: fill freed or uninitialized memory but preserve the first `page->debug_offset` |
24 | | // bytes (e.g. `ob_tid` and the reference count fields that lock-free readers may still access) |
25 | | static inline void mi_debug_fill(mi_page_t* page, mi_block_t* block, int c, size_t size) { |
26 | | size_t offset = (size_t)page->debug_offset; |
27 | | if (offset < size) { |
28 | | memset((char*)block + offset, c, size - offset); |
29 | | } |
30 | | } |
31 | | #endif |
32 | | |
33 | | #define MI_IN_ALLOC_C |
34 | | #include "alloc-override.c" |
35 | | #include "free.c" |
36 | | #undef MI_IN_ALLOC_C |
37 | | |
38 | | // ------------------------------------------------------ |
39 | | // Allocation |
40 | | // ------------------------------------------------------ |
41 | | |
42 | | // Fast allocation in a page: just pop from the free list. |
43 | | // Fall back to generic allocation only if the list is empty. |
44 | | // Note: in release mode the (inlined) routine is about 7 instructions with a single test. |
45 | | extern inline void* _mi_page_malloc_zero(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept |
46 | 0 | { |
47 | 0 | mi_assert_internal(size >= MI_PADDING_SIZE); |
48 | 0 | mi_assert_internal(page->block_size == 0 /* empty heap */ || mi_page_block_size(page) >= size); |
49 | | |
50 | | // check the free list |
51 | 0 | mi_block_t* const block = page->free; |
52 | 0 | if mi_unlikely(block == NULL) { |
53 | 0 | return _mi_malloc_generic(heap, size, zero, 0, ppage); |
54 | 0 | } |
55 | 0 | mi_assert_internal(block != NULL && _mi_ptr_page(block) == page); |
56 | 0 | if (ppage != NULL) { *ppage = page; }; |
57 | | // pop from the free list |
58 | 0 | page->free = mi_block_next(page, block); |
59 | 0 | page->used++; |
60 | 0 | mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page); |
61 | 0 | mi_assert_internal(page->block_size < MI_MAX_ALIGN_SIZE || _mi_is_aligned(block, MI_MAX_ALIGN_SIZE)); |
62 | |
|
63 | | #if MI_DEBUG>3 |
64 | | if (page->free_is_zero && size > sizeof(*block)) { |
65 | | mi_assert_expensive(mi_mem_is_zero(block+1,size - sizeof(*block))); |
66 | | } |
67 | | #endif |
68 | | |
69 | | // allow use of the block internally |
70 | | // note: when tracking we need to avoid ever touching the MI_PADDING since |
71 | | // that is tracked by valgrind etc. as non-accessible (through the red-zone, see `mimalloc/track.h`) |
72 | 0 | mi_track_mem_undefined(block, mi_page_usable_block_size(page)); |
73 | | |
74 | | // zero the block? note: we need to zero the full block size (issue #63) |
75 | 0 | if mi_unlikely(zero) { |
76 | 0 | mi_assert_internal(page->block_size != 0); // do not call with zero'ing for huge blocks (see _mi_malloc_generic) |
77 | | #if MI_PADDING |
78 | | mi_assert_internal(page->block_size >= MI_PADDING_SIZE); |
79 | | #endif |
80 | 0 | if (page->free_is_zero) { |
81 | 0 | block->next = 0; |
82 | 0 | mi_track_mem_defined(block, page->block_size - MI_PADDING_SIZE); |
83 | 0 | } |
84 | 0 | else { |
85 | 0 | _mi_memzero_aligned(block, page->block_size - MI_PADDING_SIZE); |
86 | 0 | } |
87 | 0 | } |
88 | |
|
89 | | #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN |
90 | | if (!zero && !mi_page_is_huge(page)) { |
91 | | mi_debug_fill(page, block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page)); |
92 | | } |
93 | | #elif (MI_SECURE!=0) |
94 | | if (!zero) { block->next = 0; } // don't leak internal data |
95 | | #endif |
96 | |
|
97 | | #if (MI_STAT>0) |
98 | | const size_t bsize = mi_page_usable_block_size(page); |
99 | | if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) { |
100 | | mi_heap_stat_increase(heap, malloc_normal, bsize); |
101 | | mi_heap_stat_counter_increase(heap, malloc_normal_count, 1); |
102 | | #if (MI_STAT>1) |
103 | | const size_t bin = _mi_bin(bsize); |
104 | | mi_heap_stat_increase(heap, malloc_bins[bin], 1); |
105 | | mi_heap_stat_increase(heap, malloc_requested, size - MI_PADDING_SIZE); |
106 | | #endif |
107 | | } |
108 | | #endif |
109 | |
|
110 | | #if MI_PADDING // && !MI_TRACK_ENABLED |
111 | | mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page)); |
112 | | ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE)); |
113 | | #if (MI_DEBUG>=2) |
114 | | mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta)); |
115 | | #endif |
116 | | mi_track_mem_defined(padding,sizeof(mi_padding_t)); // note: re-enable since mi_page_usable_block_size may set noaccess |
117 | | padding->canary = mi_ptr_encode_canary(page,block,page->keys); |
118 | | padding->delta = (uint32_t)(delta); |
119 | | #if MI_PADDING_CHECK |
120 | | if (!mi_page_is_huge(page)) { |
121 | | uint8_t* fill = (uint8_t*)padding - delta; |
122 | | const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes |
123 | | for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; } |
124 | | } |
125 | | #endif |
126 | | #endif |
127 | |
|
128 | 0 | return block; |
129 | 0 | } |
130 | | |
131 | | // extra entries for improved efficiency in `alloc-aligned.c`. |
132 | 0 | extern void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept { |
133 | 0 | return _mi_page_malloc_zero(heap,page,size,false,NULL); |
134 | 0 | } |
135 | 0 | extern void* _mi_page_malloc_zeroed(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept { |
136 | 0 | return _mi_page_malloc_zero(heap,page,size,true,NULL); |
137 | 0 | } |
138 | | |
139 | 0 | static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept { |
140 | 0 | mi_assert(heap != NULL); |
141 | 0 | mi_assert(size <= MI_SMALL_SIZE_MAX); |
142 | | #if MI_DEBUG |
143 | | const uintptr_t tid = _mi_thread_id(); |
144 | | mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local |
145 | | #endif |
146 | | #if (MI_PADDING || MI_GUARDED) |
147 | | if (size == 0) { size = sizeof(void*); } |
148 | | #endif |
149 | | #if MI_GUARDED |
150 | | if (mi_heap_malloc_use_guarded(heap,size)) { |
151 | | return _mi_heap_malloc_guarded(heap, size, zero, ppage); |
152 | | } |
153 | | #endif |
154 | | |
155 | | // get page in constant time, and allocate from it |
156 | 0 | mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE); |
157 | 0 | void* const p = _mi_page_malloc_zero(heap, page, size + MI_PADDING_SIZE, zero, ppage); |
158 | 0 | mi_track_malloc(p,size,zero); |
159 | |
|
160 | | #if MI_DEBUG>3 |
161 | | if (p != NULL && zero) { |
162 | | mi_assert_expensive(mi_mem_is_zero(p, size)); |
163 | | } |
164 | | #endif |
165 | 0 | return p; |
166 | 0 | } |
167 | | |
168 | | // allocate a small block |
169 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept { |
170 | 0 | return mi_heap_malloc_small_zero(heap, size, false, NULL); |
171 | 0 | } |
172 | | |
173 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept { |
174 | 0 | return mi_heap_malloc_small_zero(heap, size, true, NULL); |
175 | 0 | } |
176 | | |
177 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept { |
178 | 0 | return mi_heap_malloc_small(mi_prim_get_default_heap(), size); |
179 | 0 | } |
180 | | |
181 | | // The main allocation function |
182 | 0 | extern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, mi_page_t** ppage) mi_attr_noexcept { |
183 | | // fast path for small objects |
184 | 0 | if mi_likely(size <= MI_SMALL_SIZE_MAX) { |
185 | 0 | mi_assert_internal(huge_alignment == 0); |
186 | 0 | return mi_heap_malloc_small_zero(heap, size, zero, ppage); |
187 | 0 | } |
188 | | #if MI_GUARDED |
189 | | else if (huge_alignment==0 && mi_heap_malloc_use_guarded(heap,size)) { |
190 | | return _mi_heap_malloc_guarded(heap, size, zero, ppage); |
191 | | } |
192 | | #endif |
193 | 0 | else { |
194 | | // regular allocation |
195 | 0 | mi_assert(heap!=NULL); |
196 | 0 | mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local |
197 | 0 | void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment, ppage); // note: size can overflow but it is detected in malloc_generic |
198 | 0 | mi_track_malloc(p,size,zero); |
199 | |
|
200 | | #if MI_DEBUG>3 |
201 | | if (p != NULL && zero) { |
202 | | mi_assert_expensive(mi_mem_is_zero(p, size)); |
203 | | } |
204 | | #endif |
205 | 0 | return p; |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | 0 | extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept { |
210 | 0 | return _mi_heap_malloc_zero_ex(heap, size, zero, 0, NULL); |
211 | 0 | } |
212 | | |
213 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { |
214 | 0 | return _mi_heap_malloc_zero(heap, size, false); |
215 | 0 | } |
216 | | |
217 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept { |
218 | 0 | return mi_heap_malloc(mi_prim_get_default_heap(), size); |
219 | 0 | } |
220 | | |
221 | | // zero initialized small block |
222 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept { |
223 | 0 | return mi_heap_zalloc_small(mi_prim_get_default_heap(), size); |
224 | 0 | } |
225 | | |
226 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { |
227 | 0 | return _mi_heap_malloc_zero(heap, size, true); |
228 | 0 | } |
229 | | |
230 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept { |
231 | 0 | return mi_heap_zalloc(mi_prim_get_default_heap(),size); |
232 | 0 | } |
233 | | |
234 | | |
235 | 0 | mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { |
236 | 0 | size_t total; |
237 | 0 | if (mi_count_size_overflow(count,size,&total)) return NULL; |
238 | 0 | return mi_heap_zalloc(heap,total); |
239 | 0 | } |
240 | | |
241 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept { |
242 | 0 | return mi_heap_calloc(mi_prim_get_default_heap(),count,size); |
243 | 0 | } |
244 | | |
245 | 0 | static void* mi_ublock_size( void* p, mi_page_t* page, size_t* pblock_size ) { |
246 | 0 | mi_assert_internal(page == _mi_ptr_page(p)); |
247 | 0 | if (pblock_size!=NULL) { |
248 | 0 | if (p!=NULL) { *pblock_size = mi_page_block_size(page); } |
249 | 0 | } |
250 | 0 | return p; |
251 | 0 | } |
252 | | |
253 | | // Return the block size |
254 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* pblock_size) mi_attr_noexcept { |
255 | 0 | mi_page_t* page; |
256 | 0 | void* p = mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, false, &page); |
257 | 0 | return mi_ublock_size(p,page,pblock_size); |
258 | 0 | } |
259 | | |
260 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc_small(size_t size, size_t* pblock_size) mi_attr_noexcept { |
261 | 0 | mi_page_t* page; |
262 | 0 | void* p = mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, true, &page); |
263 | 0 | return mi_ublock_size(p,page,pblock_size); |
264 | 0 | } |
265 | | |
266 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_heap_umalloc(mi_heap_t* heap, size_t size, size_t* pblock_size) mi_attr_noexcept { |
267 | 0 | mi_page_t* page; |
268 | 0 | void* p = _mi_heap_malloc_zero_ex(heap, size, false, 0, &page); |
269 | 0 | return mi_ublock_size(p,page,pblock_size); |
270 | 0 | } |
271 | | |
272 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_umalloc(size_t size, size_t* pblock_size) mi_attr_noexcept { |
273 | 0 | return mi_heap_umalloc(mi_prim_get_default_heap(), size, pblock_size); |
274 | 0 | } |
275 | | |
276 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_uzalloc(size_t size, size_t* pblock_size) mi_attr_noexcept { |
277 | 0 | mi_page_t* page; |
278 | 0 | void* p = _mi_heap_malloc_zero_ex(mi_prim_get_default_heap(), size, true, 0, &page); |
279 | 0 | return mi_ublock_size(p,page,pblock_size); |
280 | 0 | } |
281 | | |
282 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* pblock_size) mi_attr_noexcept { |
283 | 0 | size_t total; |
284 | 0 | if (mi_count_size_overflow(count,size,&total)) return NULL; |
285 | 0 | return mi_uzalloc(total, pblock_size); |
286 | 0 | } |
287 | | |
288 | | // Uninitialized `calloc` |
289 | 0 | mi_decl_nodiscard extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { |
290 | 0 | size_t total; |
291 | 0 | if (mi_count_size_overflow(count, size, &total)) return NULL; |
292 | 0 | return mi_heap_malloc(heap, total); |
293 | 0 | } |
294 | | |
295 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept { |
296 | 0 | return mi_heap_mallocn(mi_prim_get_default_heap(),count,size); |
297 | 0 | } |
298 | | |
299 | | // Expand (or shrink) in place (or fail) |
300 | 0 | void* mi_expand(void* p, size_t newsize) mi_attr_noexcept { |
301 | | #if MI_PADDING |
302 | | // we do not shrink/expand with padding enabled |
303 | | MI_UNUSED(p); MI_UNUSED(newsize); |
304 | | return NULL; |
305 | | #else |
306 | 0 | if (p == NULL) return NULL; |
307 | 0 | const mi_page_t* const page = mi_validate_ptr_page(p,"mi_expand"); |
308 | 0 | const size_t size = _mi_page_usable_size(page,p); |
309 | 0 | if (newsize > size) return NULL; |
310 | 0 | return p; // it fits |
311 | 0 | #endif |
312 | 0 | } |
313 | | |
314 | 0 | void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero, size_t* pblock_size_pre, size_t* pblock_size_post) mi_attr_noexcept { |
315 | | // if p == NULL then behave as malloc. |
316 | | // else if size == 0 then reallocate to a zero-sized block (and don't return NULL, just as mi_malloc(0)). |
317 | | // (this means that returning NULL always indicates an error, and `p` will not have been freed in that case.) |
318 | 0 | const mi_page_t* page; |
319 | 0 | size_t size; |
320 | 0 | if (p==NULL) { |
321 | 0 | page = NULL; |
322 | 0 | size = 0; |
323 | 0 | if (pblock_size_pre!=NULL) { *pblock_size_pre = 0; } |
324 | 0 | } |
325 | 0 | else { |
326 | 0 | page = mi_validate_ptr_page(p,"mi_realloc"); |
327 | 0 | if mi_unlikely(page==NULL) { // invalid pointer |
328 | 0 | if (pblock_size_pre!=NULL) { *pblock_size_pre = 0; } |
329 | 0 | if (pblock_size_post!=NULL) { *pblock_size_post = 0; } |
330 | 0 | return NULL; |
331 | 0 | } |
332 | 0 | size = _mi_page_usable_size(page,p); |
333 | 0 | if (pblock_size_pre!=NULL) { *pblock_size_pre = mi_page_block_size(page); } |
334 | 0 | } |
335 | 0 | if mi_unlikely(newsize <= size && newsize >= (size / 2) && newsize > 0) { // note: newsize must be > 0 or otherwise we return NULL for realloc(NULL,0) |
336 | 0 | mi_assert_internal(p!=NULL); |
337 | | // todo: do not track as the usable size is still the same in the free; adjust potential padding? |
338 | | // mi_track_resize(p,size,newsize) |
339 | | // if (newsize < size) { mi_track_mem_noaccess((uint8_t*)p + newsize, size - newsize); } |
340 | 0 | if (pblock_size_post!=NULL) { *pblock_size_post = mi_page_block_size(page); } |
341 | 0 | return p; // reallocation still fits and not more than 50% waste |
342 | 0 | } |
343 | | // note: we don't zero allocate upfront so we only zero initialize the expanded part |
344 | 0 | mi_page_t* newpage; // use block_size for zero-ing, issue #763 |
345 | 0 | void* const newp = _mi_heap_malloc_zero_ex(heap,newsize,false /* no zero */,0,&newpage); |
346 | 0 | if mi_likely(newp != NULL) { |
347 | 0 | if (pblock_size_post!=NULL) { *pblock_size_post = mi_page_block_size(newpage); } |
348 | 0 | const size_t copy_size = (newsize > size ? size : newsize); |
349 | 0 | const size_t zero_start = _mi_align_down( (copy_size >= sizeof(intptr_t) ? copy_size - sizeof(intptr_t) : 0), sizeof(intptr_t)); // also set last word in the previous allocation to zero to ensure any padding is zero-initialized |
350 | 0 | const size_t usable = _mi_page_usable_size(newpage,newp); |
351 | 0 | mi_assert_internal(usable >= newsize); |
352 | 0 | if (zero && usable > zero_start) { |
353 | 0 | _mi_memzero_aligned((uint8_t*)newp + zero_start, usable - zero_start); |
354 | 0 | } |
355 | 0 | else if (newsize == 0) { |
356 | 0 | ((uint8_t*)newp)[0] = 0; // work around for applications that expect zero-reallocation to be zero initialized (issue #725) |
357 | 0 | } |
358 | 0 | if mi_likely(p != NULL) { |
359 | 0 | mi_track_mem_defined(p,copy_size); // _mi_useable_size may be too large for byte precise memory tracking.. |
360 | 0 | _mi_memcpy_aligned(newp, p, copy_size); |
361 | 0 | mi_free(p); // only free the original pointer if successful |
362 | 0 | } |
363 | 0 | } |
364 | 0 | return newp; |
365 | 0 | } |
366 | | |
367 | 0 | mi_decl_nodiscard void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { |
368 | 0 | return _mi_heap_realloc_zero(heap, p, newsize, false, NULL, NULL); |
369 | 0 | } |
370 | | |
371 | 0 | mi_decl_nodiscard void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { |
372 | 0 | size_t total; |
373 | 0 | if (mi_count_size_overflow(count, size, &total)) return NULL; |
374 | 0 | return mi_heap_realloc(heap, p, total); |
375 | 0 | } |
376 | | |
377 | | |
378 | | // Reallocate but free `p` on errors |
379 | 0 | mi_decl_nodiscard void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { |
380 | 0 | void* newp = mi_heap_realloc(heap, p, newsize); |
381 | 0 | if (newp==NULL && p!=NULL) mi_free(p); |
382 | 0 | return newp; |
383 | 0 | } |
384 | | |
385 | 0 | mi_decl_nodiscard void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { |
386 | 0 | return _mi_heap_realloc_zero(heap, p, newsize, true, NULL, NULL); |
387 | 0 | } |
388 | | |
389 | 0 | mi_decl_nodiscard void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { |
390 | 0 | size_t total; |
391 | 0 | if (mi_count_size_overflow(count, size, &total)) return NULL; |
392 | 0 | return mi_heap_rezalloc(heap, p, total); |
393 | 0 | } |
394 | | |
395 | | |
396 | 0 | mi_decl_nodiscard void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept { |
397 | 0 | return mi_heap_realloc(mi_prim_get_default_heap(),p,newsize); |
398 | 0 | } |
399 | | |
400 | 0 | mi_decl_nodiscard void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept { |
401 | 0 | return mi_heap_reallocn(mi_prim_get_default_heap(),p,count,size); |
402 | 0 | } |
403 | | |
404 | 0 | mi_decl_nodiscard void* mi_urealloc(void* p, size_t newsize, size_t* pblock_size_pre, size_t* pblock_size_post) mi_attr_noexcept { |
405 | 0 | return _mi_heap_realloc_zero(mi_prim_get_default_heap(),p,newsize, false, pblock_size_pre, pblock_size_post); |
406 | 0 | } |
407 | | |
408 | | // Reallocate but free `p` on errors |
409 | 0 | mi_decl_nodiscard void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept { |
410 | 0 | return mi_heap_reallocf(mi_prim_get_default_heap(),p,newsize); |
411 | 0 | } |
412 | | |
413 | 0 | mi_decl_nodiscard void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept { |
414 | 0 | return mi_heap_rezalloc(mi_prim_get_default_heap(), p, newsize); |
415 | 0 | } |
416 | | |
417 | 0 | mi_decl_nodiscard void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept { |
418 | 0 | return mi_heap_recalloc(mi_prim_get_default_heap(), p, count, size); |
419 | 0 | } |
420 | | |
421 | | |
422 | | |
423 | | // ------------------------------------------------------ |
424 | | // strdup, strndup, and realpath |
425 | | // ------------------------------------------------------ |
426 | | |
427 | | // `strdup` using mi_malloc |
428 | 0 | mi_decl_nodiscard mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept { |
429 | 0 | if (s == NULL) return NULL; |
430 | 0 | size_t len = _mi_strlen(s); |
431 | 0 | if (len > MI_MAX_ALLOC_SIZE - 1) return NULL; // prevent overflow on len+1 |
432 | 0 | char* t = (char*)mi_heap_malloc(heap,len+1); |
433 | 0 | if (t == NULL) return NULL; |
434 | 0 | _mi_memcpy(t, s, len); |
435 | 0 | t[len] = 0; |
436 | 0 | return t; |
437 | 0 | } |
438 | | |
439 | 0 | mi_decl_nodiscard mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept { |
440 | 0 | return mi_heap_strdup(mi_prim_get_default_heap(), s); |
441 | 0 | } |
442 | | |
443 | | // `strndup` using mi_malloc |
444 | 0 | mi_decl_nodiscard mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept { |
445 | 0 | if (s == NULL) return NULL; |
446 | 0 | const size_t len = _mi_strnlen(s,n); // len <= n |
447 | 0 | if (len > MI_MAX_ALLOC_SIZE - 1) return NULL; // prevent overflow on len+1 |
448 | 0 | char* t = (char*)mi_heap_malloc(heap, len+1); |
449 | 0 | if (t == NULL) return NULL; |
450 | 0 | _mi_memcpy(t, s, len); |
451 | 0 | t[len] = 0; |
452 | 0 | return t; |
453 | 0 | } |
454 | | |
455 | 0 | mi_decl_nodiscard mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept { |
456 | 0 | return mi_heap_strndup(mi_prim_get_default_heap(),s,n); |
457 | 0 | } |
458 | | |
459 | | #ifndef __wasi__ |
460 | | // `realpath` using mi_malloc |
461 | | #ifdef _WIN32 |
462 | | #ifndef PATH_MAX |
463 | | #define PATH_MAX MAX_PATH |
464 | | #endif |
465 | | |
466 | | mi_decl_nodiscard mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { |
467 | | // todo: use GetFullPathNameW to allow longer file names |
468 | | if (fname==NULL || *fname==0) { |
469 | | errno = EINVAL; |
470 | | return NULL; |
471 | | } |
472 | | char buf[PATH_MAX]; |
473 | | DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL); |
474 | | if (res == 0) { |
475 | | DWORD err = GetLastError(); |
476 | | switch (err) { |
477 | | case ERROR_LOCK_VIOLATION: |
478 | | case ERROR_SHARING_VIOLATION: |
479 | | case ERROR_INVALID_ACCESS: errno = EACCES; break; |
480 | | case ERROR_INVALID_HANDLE: |
481 | | case ERROR_INVALID_FUNCTION: errno = EINVAL; break; |
482 | | case ERROR_PATH_NOT_FOUND: errno = ENOTDIR; break; |
483 | | case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; |
484 | | case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break; |
485 | | default: errno = EIO; |
486 | | } |
487 | | return NULL; |
488 | | } |
489 | | else if (res > PATH_MAX) { |
490 | | errno = ENAMETOOLONG; |
491 | | return NULL; |
492 | | } |
493 | | else if (resolved_name != NULL) { |
494 | | return resolved_name; |
495 | | } |
496 | | else { |
497 | | return mi_heap_strndup(heap, buf, PATH_MAX); |
498 | | } |
499 | | } |
500 | | |
501 | | #else |
502 | | |
503 | | #include <unistd.h> // pathconf |
504 | | |
505 | 0 | static size_t mi_path_max(void) { |
506 | 0 | static _Atomic(size_t) path_max = 0; |
507 | 0 | size_t pmax = mi_atomic_load_acquire(&path_max); |
508 | 0 | if (pmax == 0) { |
509 | 0 | long m = 0; |
510 | 0 | #ifdef _PC_PATH_MAX |
511 | 0 | m = pathconf("/",_PC_PATH_MAX); |
512 | 0 | #endif |
513 | 0 | if (m <= 0) pmax = 4096; // guess |
514 | 0 | else if (m < 256) pmax = 256; // at least 256 |
515 | 0 | else if (m > 64*1024) pmax = 64*1024; // at most 64 KiB |
516 | 0 | else pmax = m; |
517 | 0 | size_t expected = 0; |
518 | 0 | mi_atomic_cas_strong_acq_rel(&path_max, &expected, pmax); |
519 | 0 | } |
520 | 0 | return pmax; |
521 | 0 | } |
522 | | |
523 | 0 | char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { |
524 | 0 | if (resolved_name != NULL) { |
525 | 0 | return realpath(fname,resolved_name); |
526 | 0 | } |
527 | 0 | else { |
528 | | /* |
529 | | char* rname = realpath(fname, NULL); |
530 | | if (rname == NULL) return NULL; |
531 | | char* result = mi_heap_strdup(heap, rname); |
532 | | mi_cfree(rname); // note: may leak the original pointer if allocated internally with the system allocator |
533 | | // note: with ASAN realpath is intercepted and mi_cfree may leak the returned pointer :-( |
534 | | return result; |
535 | | */ |
536 | 0 | const size_t n = mi_path_max(); |
537 | 0 | char* const buf = (char*)mi_zalloc(n+1); |
538 | 0 | if (buf == NULL) { |
539 | 0 | errno = ENOMEM; |
540 | 0 | return NULL; |
541 | 0 | } |
542 | 0 | char* rname = realpath(fname,buf); |
543 | 0 | char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL` |
544 | 0 | mi_free(buf); |
545 | 0 | return result; |
546 | 0 | } |
547 | 0 | } |
548 | | #endif |
549 | | |
550 | 0 | mi_decl_nodiscard mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept { |
551 | 0 | return mi_heap_realpath(mi_prim_get_default_heap(),fname,resolved_name); |
552 | 0 | } |
553 | | #endif |
554 | | |
555 | | /*------------------------------------------------------- |
556 | | C++ new and new_aligned |
557 | | The standard requires calling into `get_new_handler` and |
558 | | throwing the bad_alloc exception on failure. If we compile |
559 | | with a C++ compiler we can implement this precisely. If we |
560 | | use a C compiler we cannot throw a `bad_alloc` exception |
561 | | but we call `abort` instead (i.e. not returning). |
562 | | Also, the standard requires calling the new handler until |
563 | | it returns false, but we limit the total calls. |
564 | | -------------------------------------------------------*/ |
565 | 0 | #define MI_TRY_NEW_MAX (4) |
566 | | |
567 | | #ifdef __cplusplus |
568 | | #include <new> |
569 | | static bool mi_try_new_handler(bool nothrow) { |
570 | | #if defined(_MSC_VER) || (__cplusplus >= 201103L) |
571 | | std::new_handler h = std::get_new_handler(); |
572 | | #else |
573 | | std::new_handler h = std::set_new_handler(); |
574 | | std::set_new_handler(h); |
575 | | #endif |
576 | | if (h==NULL) { |
577 | | _mi_error_message(ENOMEM, "out of memory in 'new'"); |
578 | | #if defined(_CPPUNWIND) || defined(__cpp_exceptions) // exceptions are not always enabled |
579 | | if (!nothrow) { |
580 | | throw std::bad_alloc(); |
581 | | } |
582 | | #else |
583 | | MI_UNUSED(nothrow); |
584 | | #endif |
585 | | return false; |
586 | | } |
587 | | else if (!nothrow) { |
588 | | h(); |
589 | | return true; |
590 | | } |
591 | | else { |
592 | | try { |
593 | | h(); |
594 | | } |
595 | | catch(...) { // swallow std::bad_alloc |
596 | | return false; // stop trying |
597 | | } |
598 | | return true; |
599 | | } |
600 | | } |
601 | | #else |
602 | | typedef void (*std_new_handler_t)(void); |
603 | | |
604 | | #if (defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER))) // exclude clang-cl, see issue #631 |
605 | 0 | std_new_handler_t __attribute__((weak)) _ZSt15get_new_handlerv(void) { |
606 | 0 | return NULL; |
607 | 0 | } |
608 | 0 | static std_new_handler_t mi_get_new_handler(void) { |
609 | 0 | return _ZSt15get_new_handlerv(); |
610 | 0 | } |
611 | | #else |
612 | | // note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`. |
613 | | static std_new_handler_t mi_get_new_handler(void) { |
614 | | return NULL; |
615 | | } |
616 | | #endif |
617 | | |
618 | 0 | static bool mi_try_new_handler(bool nothrow) { |
619 | 0 | std_new_handler_t h = mi_get_new_handler(); |
620 | 0 | if (h==NULL) { |
621 | 0 | _mi_error_message(ENOMEM, "out of memory in 'new'"); |
622 | 0 | if (!nothrow) { |
623 | 0 | abort(); // cannot throw in plain C, use abort |
624 | 0 | } |
625 | 0 | return false; |
626 | 0 | } |
627 | 0 | else { |
628 | 0 | h(); |
629 | 0 | return true; |
630 | 0 | } |
631 | 0 | } |
632 | | #endif |
633 | | |
634 | 0 | mi_decl_export mi_decl_noinline void* mi_heap_try_new(mi_heap_t* heap, size_t size, bool nothrow ) { |
635 | 0 | void* p = NULL; |
636 | 0 | for(int i = 0; i < MI_TRY_NEW_MAX && p == NULL && mi_try_new_handler(nothrow); i++) { |
637 | 0 | if (size > MI_MAX_ALLOC_SIZE) return NULL; // call try_new_handler at least once |
638 | 0 | p = mi_heap_malloc(heap,size); |
639 | 0 | } |
640 | 0 | return p; |
641 | 0 | } |
642 | | |
643 | 0 | static mi_decl_noinline void* mi_try_new(size_t size, bool nothrow) { |
644 | 0 | return mi_heap_try_new(mi_prim_get_default_heap(), size, nothrow); |
645 | 0 | } |
646 | | |
647 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) { |
648 | 0 | void* p = mi_heap_malloc(heap,size); |
649 | 0 | if mi_unlikely(p == NULL) return mi_heap_try_new(heap, size, false); |
650 | 0 | return p; |
651 | 0 | } |
652 | | |
653 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_new(size_t size) { |
654 | 0 | return mi_heap_alloc_new(mi_prim_get_default_heap(), size); |
655 | 0 | } |
656 | | |
657 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) { |
658 | 0 | size_t total; |
659 | 0 | if mi_unlikely(mi_count_size_overflow(count, size, &total)) { |
660 | 0 | mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc |
661 | 0 | return NULL; |
662 | 0 | } |
663 | 0 | else { |
664 | 0 | return mi_heap_alloc_new(heap,total); |
665 | 0 | } |
666 | 0 | } |
667 | | |
668 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_new_n(size_t count, size_t size) { |
669 | 0 | return mi_heap_alloc_new_n(mi_prim_get_default_heap(), count, size); |
670 | 0 | } |
671 | | |
672 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept { |
673 | 0 | void* p = mi_malloc(size); |
674 | 0 | if mi_unlikely(p == NULL) return mi_try_new(size, true); |
675 | 0 | return p; |
676 | 0 | } |
677 | | |
678 | 0 | static mi_decl_noinline void* mi_try_new_aligned(size_t size, size_t alignment, bool nothrow) { |
679 | 0 | void* p = NULL; |
680 | 0 | for(int i = 0; i < MI_TRY_NEW_MAX && p==NULL && mi_try_new_handler(nothrow); i++) { |
681 | 0 | if (!mi_alignment_is_valid(alignment)) return NULL; |
682 | 0 | p = mi_malloc_aligned(size,alignment); |
683 | 0 | } |
684 | 0 | return p; |
685 | 0 | } |
686 | | |
687 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) { |
688 | 0 | void* p = mi_malloc_aligned(size, alignment); |
689 | 0 | if mi_unlikely(p==NULL) return mi_try_new_aligned(size,alignment,false); |
690 | 0 | return p; |
691 | 0 | } |
692 | | |
693 | 0 | mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept { |
694 | 0 | void* p = mi_malloc_aligned(size, alignment); |
695 | 0 | if mi_unlikely(p==NULL) return mi_try_new_aligned(size,alignment,true); |
696 | 0 | return p; |
697 | 0 | } |
698 | | |
699 | 0 | static mi_decl_noinline void* mi_try_new_realloc(void* p, size_t newsize) { |
700 | 0 | void* q = NULL; |
701 | 0 | for(int i = 0; i < MI_TRY_NEW_MAX && q==NULL && mi_try_new_handler(false); i++) { |
702 | 0 | if (newsize > MI_MAX_ALLOC_SIZE) return NULL; |
703 | 0 | q = mi_realloc(p,newsize); |
704 | 0 | } |
705 | 0 | return q; |
706 | 0 | } |
707 | | |
708 | 0 | mi_decl_nodiscard void* mi_new_realloc(void* p, size_t newsize) { |
709 | 0 | void* q = mi_realloc(p, newsize); |
710 | 0 | if (q == NULL) return mi_try_new_realloc(p,newsize); |
711 | 0 | return q; |
712 | 0 | } |
713 | | |
714 | 0 | mi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) { |
715 | 0 | size_t total; |
716 | 0 | if mi_unlikely(mi_count_size_overflow(newcount, size, &total)) { |
717 | 0 | mi_try_new_handler(false); |
718 | 0 | return NULL; |
719 | 0 | } |
720 | 0 | else { |
721 | 0 | return mi_new_realloc(p, total); |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | | #if MI_GUARDED |
726 | | // We always allocate a guarded allocation at an offset (`mi_page_has_aligned` will be true). |
727 | | // We then set the first word of the block to `0` for regular offset aligned allocations (in `alloc-aligned.c`) |
728 | | // and the first word to `~0` for guarded allocations to have a correct `mi_usable_size` |
729 | | |
730 | | static void* mi_block_ptr_set_guarded(mi_block_t* block, size_t obj_size, size_t* usable_size) { |
731 | | // TODO: we can still make padding work by moving it out of the guard page area |
732 | | mi_page_t* const page = _mi_ptr_page(block); |
733 | | mi_page_set_has_aligned(page, true); |
734 | | block->next = MI_BLOCK_TAG_GUARDED; |
735 | | |
736 | | // set guard page at the end of the block |
737 | | mi_segment_t* const segment = _mi_page_segment(page); |
738 | | const size_t block_size = mi_page_block_size(page); // must use `block_size` to match `mi_free_local` |
739 | | const size_t os_page_size = _mi_os_page_size(); |
740 | | mi_assert_internal(block_size >= obj_size + os_page_size + sizeof(mi_block_t)); |
741 | | if (block_size < obj_size + os_page_size + sizeof(mi_block_t)) { |
742 | | // should never happen |
743 | | mi_free(block); |
744 | | return NULL; |
745 | | } |
746 | | uint8_t* guard_page = (uint8_t*)block + block_size - os_page_size; |
747 | | mi_assert_internal(_mi_is_aligned(guard_page, os_page_size)); |
748 | | if mi_likely(segment->allow_decommit && _mi_is_aligned(guard_page, os_page_size)) { |
749 | | const bool ok = _mi_os_protect(guard_page, os_page_size); |
750 | | if mi_unlikely(!ok) { |
751 | | _mi_warning_message("failed to set a guard page behind an object (object %p of size %zu)\n", block, block_size); |
752 | | } |
753 | | } |
754 | | else { |
755 | | _mi_warning_message("unable to set a guard page behind an object due to pinned memory (large OS pages?) (object %p of size %zu)\n", block, block_size); |
756 | | } |
757 | | |
758 | | // align pointer just in front of the guard page |
759 | | size_t offset = block_size - os_page_size - obj_size; |
760 | | mi_assert_internal(offset > sizeof(mi_block_t)); |
761 | | if (offset > MI_BLOCK_ALIGNMENT_MAX) { |
762 | | // give up to place it right in front of the guard page if the offset is too large for unalignment |
763 | | offset = MI_BLOCK_ALIGNMENT_MAX; |
764 | | } |
765 | | uint8_t* const p = (uint8_t*)block + offset; |
766 | | mi_assert_internal(p == guard_page - obj_size || offset == MI_BLOCK_ALIGNMENT_MAX); |
767 | | if (usable_size != NULL) { *usable_size = (guard_page - p); mi_assert_internal(mi_usable_size(p)==*usable_size); } |
768 | | mi_track_align(block, p, offset, obj_size); |
769 | | mi_track_mem_defined(block, sizeof(mi_block_t)); |
770 | | return p; |
771 | | } |
772 | | |
773 | | mi_decl_restrict void* _mi_heap_malloc_guarded(mi_heap_t* heap, size_t size, bool zero, mi_page_t** ppage) mi_attr_noexcept |
774 | | { |
775 | | // allocate multiple of page size ending in a guard page |
776 | | // ensure minimal alignment requirement? |
777 | | if mi_unlikely(size >= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) { // check up front so the `req_size` won't overflow |
778 | | _mi_error_message(EOVERFLOW, "(guarded) allocation request is too large (%zu bytes)\n", size); |
779 | | return NULL; |
780 | | } |
781 | | const size_t os_page_size = _mi_os_page_size(); |
782 | | const size_t obj_size = (mi_option_is_enabled(mi_option_guarded_precise) ? size : _mi_align_up(size, MI_MAX_ALIGN_SIZE)); |
783 | | const size_t bsize = _mi_align_up(_mi_align_up(obj_size, MI_MAX_ALIGN_SIZE) + sizeof(mi_block_t), MI_MAX_ALIGN_SIZE); |
784 | | const size_t req_size = _mi_align_up(bsize + os_page_size, os_page_size); |
785 | | mi_block_t* const block = (mi_block_t*)_mi_malloc_generic(heap, req_size, false /* don't zero */, 0 /* huge_alignment */, ppage); |
786 | | if (block==NULL) return NULL; |
787 | | size_t usable_size = 0; |
788 | | void* const p = mi_block_ptr_set_guarded(block, obj_size, &usable_size); |
789 | | if (p == NULL) return NULL; |
790 | | if (zero) { |
791 | | _mi_memzero(p,obj_size); // we have to zero afterwards as padding might have written inside the block (if the `blocksize > reqsize + os_page_size`) |
792 | | } |
793 | | |
794 | | // stats |
795 | | mi_track_malloc(p, usable_size, zero); |
796 | | if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); } |
797 | | _mi_stat_counter_increase(&heap->tld->stats.malloc_guarded_count, 1); |
798 | | #if MI_STAT>1 |
799 | | // adjust stats to only count the allocated size of the block (and not the guard page) |
800 | | mi_heap_stat_adjust_decrease(heap, malloc_requested, req_size); |
801 | | mi_heap_stat_increase(heap, malloc_requested, size); |
802 | | #endif |
803 | | #if MI_DEBUG>3 |
804 | | if (zero) { |
805 | | mi_assert_expensive(mi_mem_is_zero(p, size)); |
806 | | } |
807 | | #endif |
808 | | return p; |
809 | | } |
810 | | #endif |
811 | | |
812 | | // ------------------------------------------------------ |
813 | | // ensure explicit external inline definitions are emitted! |
814 | | // ------------------------------------------------------ |
815 | | |
816 | | #ifdef __cplusplus |
817 | | void* _mi_externs[] = { |
818 | | (void*)&_mi_page_malloc, |
819 | | (void*)&_mi_page_malloc_zero, |
820 | | (void*)&_mi_heap_malloc_zero, |
821 | | (void*)&_mi_heap_malloc_zero_ex, |
822 | | (void*)&mi_malloc, |
823 | | (void*)&mi_malloc_small, |
824 | | (void*)&mi_zalloc_small, |
825 | | (void*)&mi_heap_malloc, |
826 | | (void*)&mi_heap_zalloc, |
827 | | (void*)&mi_heap_malloc_small, |
828 | | // (void*)&mi_heap_alloc_new, |
829 | | // (void*)&mi_heap_alloc_new_n |
830 | | }; |
831 | | #endif |