2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
30 #ifdef HAVE_SYS_RESOURCE_H
31 #include <sys/resource.h>
38 #if defined(POINTER_COMPRESSION_POSSIBLE)
39 uchar_efficient_t pointer_compression_enabled
= 0;
42 #if defined(USE_AMALLOC)
43 uchar_efficient_t amalloc_enabled
= 1;
49 #define alloc_should_fail(mayfail) true
51 #define alloc_should_fail(mayfail) false
53 static bool alloc_should_fail(ajla_error_t
*mayfail
)
59 /*debug("%d", count);*/
60 if (!(rand() & 0xffff)) {
61 debug("failing allocation");
68 #if defined(HAVE_MALLOC) && HAVE_MALLOC
69 #define heap_notzero(x) (x)
71 #define heap_notzero(x) (likely((x) != 0) ? (x) : 1)
74 #define heap_malloc(x) (likely(amalloc_enabled) ? amalloc(x) : malloc(heap_notzero(x)))
75 #define heap_calloc(x) (likely(amalloc_enabled) ? acalloc(x) : calloc(1, heap_notzero(x)))
76 #define heap_realloc(x, y) (likely(amalloc_enabled) ? arealloc(x, y) : realloc(x, y))
77 #define heap_free(x) (likely(amalloc_enabled) ? afree(x) : free(cast_cpp(void *, x)))
78 #define heap_memalign(al, sz) (likely(amalloc_enabled) ? amemalign(al, sz) : do_memalign(al, sz))
79 #define heap_cmemalign(al, sz) (likely(amalloc_enabled) ? acmemalign(al, sz) : zmem(do_memalign(al, sz), sz))
80 #define heap_free_aligned(x) (likely(amalloc_enabled) ? afree(x) : do_free_aligned(x))
82 static void *zmem(void *ptr
, size_t size
)
84 if (likely(ptr
!= NULL
))
85 return memset(ptr
, 0, size
);
89 #if !defined(UNUSUAL_NO_MEMALIGN) && defined(HAVE_MEMALIGN) && defined(__DJGPP__)
90 /* DJGPP has swapped arguments */
91 static inline void *do_memalign(size_t al
, size_t sz
)
93 return memalign(heap_notzero(sz
), al
);
95 #define do_free_aligned free
96 #elif !defined(UNUSUAL_NO_MEMALIGN) && defined(HAVE_MEMALIGN) && !defined(__sun__)
97 #define do_memalign memalign
98 #define do_free_aligned free
99 #elif !defined(UNUSUAL_NO_MEMALIGN) && defined(HAVE_POSIX_MEMALIGN)
100 static inline void *do_memalign(size_t align
, size_t sz
)
102 void *ptr
= NULL
; /* avoid warning */
103 sz
= heap_notzero(sz
);
104 if (unlikely(align
< sizeof(void *)))
105 align
= sizeof(void *);
106 if (unlikely(posix_memalign(&ptr
, align
, sz
)))
110 #define do_free_aligned free
111 #elif !defined(UNUSUAL_NO_MEMALIGN) && defined(HAVE_ALIGNED_ALLOC)
112 static inline void *do_memalign(size_t align
, size_t sz
)
115 sz
= heap_notzero(sz
);
116 rsz
= round_up(sz
, align
);
117 if (unlikely(rsz
< sz
))
119 return aligned_alloc(align
, rsz
);
121 #define do_free_aligned free
123 typedef size_t align_bytes_t
;
124 static inline void *do_memalign(size_t align
, size_t sz
)
128 if (align
< HEAP_ALIGN
)
130 extra
= align
- 1 + sizeof(align_bytes_t
);
131 /*debug("align: %x, %x", sz, align);*/
132 if (unlikely(extra
!= (align_bytes_t
)extra
))
133 internal(file_line
, "do_memalign: too big alignment %"PRIuMAX
"", (uintmax_t)align
);
135 if (unlikely(sz2
< sz
))
137 p
= heap_malloc(sz2
);
140 p2
= cast_ptr(char *, p
) + sizeof(align_bytes_t
);
141 p2
= num_to_ptr(round_up(ptr_to_num(p2
), align
));
142 (cast_ptr(align_bytes_t
*, p2
))[-1] = (align_bytes_t
)(cast_ptr(char *, p2
) - cast_ptr(char *, p
));
145 static inline void do_free_aligned(void *p
)
147 align_bytes_t a
= (cast_ptr(align_bytes_t
*, p
))[-1];
148 heap_free(cast_ptr(char *, p
) - a
);
152 static void attr_cold
*oom_calloc(size_t size
, ajla_error_t
*mayfail
, position_t position
)
154 if (mayfail
== MEM_DONT_TRY_TO_FREE
)
156 while (mem_trim_cache()) {
157 void *p
= heap_calloc(size
);
161 fatal_mayfail(error_ajla(EC_ASYNC
, AJLA_ERROR_OUT_OF_MEMORY
), mayfail
, "out of memory for malloc, %"PRIuMAX
" bytes at %s", (uintmax_t)size
, position_string(position
));
165 static void attr_cold
*oom_cmemalign(size_t size
, size_t alignment
, ajla_error_t
*mayfail
, position_t position
)
167 if (mayfail
== MEM_DONT_TRY_TO_FREE
)
169 while (mem_trim_cache()) {
170 void *p
= heap_cmemalign(alignment
, size
);
174 fatal_mayfail(error_ajla(EC_ASYNC
, AJLA_ERROR_OUT_OF_MEMORY
), mayfail
, "out of memory for memalign, %"PRIuMAX
" bytes, alignment %"PRIuMAX
" at %s", (uintmax_t)size
, (uintmax_t)alignment
, position_string(position
));
178 static void attr_cold
*oom_realloc(void attr_unused
*ptr
, size_t size
, ajla_error_t
*mayfail
, position_t position
)
180 if (mayfail
== MEM_DONT_TRY_TO_FREE
)
182 while (mem_trim_cache()) {
183 void *p
= heap_realloc(ptr
, size
);
187 fatal_mayfail(error_ajla(EC_ASYNC
, AJLA_ERROR_OUT_OF_MEMORY
), mayfail
, "out of memory for realloc, %"PRIuMAX
" bytes at %s", (uintmax_t)size
, position_string(position
));
191 #define MEMORY_DEBUG_MAGIC 1
192 #define MEMORY_DEBUG_REDZONE 2
193 #define MEMORY_DEBUG_FILL 4
194 #define MEMORY_DEBUG_TRACK_BLOCKS 8
195 #define MEMORY_DEBUG_HISTOGRAM 16
197 #ifdef DEBUG_MEMORY_POSSIBLE
199 static int memory_debug
= 0;
201 #define USE_RED_ZONE (likely(memory_debug & MEMORY_DEBUG_REDZONE))
202 #define USE_FILL (likely(memory_debug & MEMORY_DEBUG_FILL))
203 #define USE_LIST (likely(memory_debug & MEMORY_DEBUG_TRACK_BLOCKS))
204 #define USE_HISTOGRAM (unlikely(memory_debug & MEMORY_DEBUG_HISTOGRAM))
208 struct histogram_entry
{
214 struct list block_list
;
217 struct histogram_entry
*histogram
;
218 size_t histogram_size
;
221 struct list free_list
;
222 struct list used_list
;
223 tls_destructor_t destructor
;
227 #define ALLOC_MAGIC 0xa110c
228 #define ALLOC_MAGIC_ALIGNED 0xa11167
229 #define ALLOC_MAGIC_FREE 0xf4ee
230 #define ALLOC_MAGIC_REALLOC 0x4ea110c
232 typedef uint32_t ah_magic_t
;
234 struct alloc_header
{
236 struct per_thread
*per_thread
;
242 ah_magic_t magic_
[1];
245 #define AH_SIZE round_up(sizeof(struct alloc_header), HEAP_ALIGN)
246 #define AH_DATA(ah) (cast_ptr(unsigned char *, ah) + AH_SIZE)
247 #define AH_MAGIC(ah) (cast_ptr(ah_magic_t *, AH_DATA(ah))[-1])
248 #define AH_RED_ZONE(ah) (AH_DATA(ah)[ah->size])
249 #define AH_MALLOC_BLOCK(ah) (cast_ptr(unsigned char *, (ah)) - (ah)->padding)
250 #define AH_FROM_PTR(ptr) cast_ptr(struct alloc_header *, (cast_ptr(unsigned char *, ptr) - AH_SIZE))
252 static struct per_thread thread1
;
253 static bool memory_threads_initialized
;
255 tls_decl(unsigned char, memory_fill
);
257 static unsigned char get_memory_fill(void)
259 if (!memory_threads_initialized
) {
260 static unsigned char memory_fill_preinit
= 0;
261 return (unsigned char)++memory_fill_preinit
;
263 unsigned char mf
= tls_get(unsigned char, memory_fill
);
265 tls_set(unsigned char, memory_fill
, mf
);
270 static inline void mem_per_thread_init(struct per_thread
*pt
)
272 list_init(&pt
->block_list
);
276 pt
->histogram_size
= 2;
277 pt
->histogram
= heap_calloc(pt
->histogram_size
* sizeof(struct histogram_entry
));
281 static void increment_histogram(struct per_thread
*pt
, size_t sz
, uintmax_t count
, position_t position
)
283 size_t old_count
, new_count
;
285 sz
= round_up(sz
, 16);
287 if (unlikely(sz
>= pt
->histogram_size
)) {
289 struct histogram_entry
*new_histogram
;
290 new_size
= pt
->histogram_size
;
292 new_size
= new_size
* 2 - 1;
293 if (unlikely(new_size
> (size_t)-1 / sizeof(struct histogram_entry
)))
295 } while (sz
>= new_size
);
296 new_histogram
= heap_calloc(new_size
* sizeof(struct histogram_entry
));
297 if (unlikely(!new_histogram
))
299 for (i
= 0; i
< pt
->histogram_size
; i
++) {
300 if (unlikely(pt
->histogram
[i
].cnt
!= 0))
301 new_histogram
[i
] = pt
->histogram
[i
];
303 heap_free(pt
->histogram
);
304 pt
->histogram
= new_histogram
;
305 pt
->histogram_size
= new_size
;
307 old_count
= pt
->histogram
[sz
].cnt
;
308 new_count
= old_count
+ count
;
309 if (unlikely(new_count
< count
))
311 pt
->histogram
[sz
].cnt
= new_count
;
312 if ((new_count
^ old_count
) >= old_count
)
313 pt
->histogram
[sz
].position
= position
;
318 static tls_decl(struct per_thread
*, mem_per_thread
);
320 static void mem_per_thread_free(struct per_thread
*pt
)
322 thread1
.bytes
+= pt
->bytes
;
323 thread1
.blocks
+= pt
->blocks
;
324 if (unlikely(thread1
.bytes
< pt
->bytes
) || unlikely(thread1
.blocks
< pt
->blocks
))
325 internal(file_line
, "mem_per_thread_free: memory counters underflow: %"PRIuMAX
", %"PRIuMAX
" < %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)thread1
.bytes
, (uintmax_t)thread1
.blocks
, (uintmax_t)pt
->bytes
, (uintmax_t)pt
->blocks
);
328 for (i
= 0; i
< pt
->histogram_size
; i
++)
329 if (unlikely(pt
->histogram
[i
].cnt
!= 0))
330 increment_histogram(&thread1
, i
, pt
->histogram
[i
].cnt
, pt
->histogram
[i
].position
);
331 heap_free(pt
->histogram
);
333 while (!list_is_empty(&pt
->block_list
)) {
334 struct alloc_header
*ah
= get_struct(pt
->block_list
.prev
, struct alloc_header
, entry
);
335 if (unlikely(ah
->per_thread
!= pt
))
336 internal(file_line
, "mem_per_thread_free: block is on wrong list: %p != %p (block allocated at %s)", ah
->per_thread
, pt
, position_string(ah
->position
));
337 ah
->per_thread
= &thread1
;
338 list_del(&ah
->entry
);
339 list_add(&thread1
.block_list
, &ah
->entry
);
341 mutex_done(&pt
->mutex
);
342 mem_free_aligned(pt
);
346 static void mem_per_thread_destructor(tls_destructor_t
*destr
)
348 struct per_thread
*pt
= get_struct(destr
, struct per_thread
, destructor
);
349 tls_set(struct per_thread
*, mem_per_thread
, &thread1
);
351 ajla_assert_lo(memory_threads_initialized
, (file_line
, "mem_per_thread_destructor called when threads are not initialized"));
353 mutex_lock(&thread1
.mutex
);
354 list_del(&pt
->used_list
);
355 list_add(&thread1
.free_list
, &pt
->free_list
);
356 mutex_unlock(&thread1
.mutex
);
360 static attr_noinline
struct per_thread
*mem_per_thread_alloc(void)
362 struct per_thread
*pt
;
364 tls_set(struct per_thread
*, mem_per_thread
, &thread1
);
365 mutex_lock(&thread1
.mutex
);
366 if (!list_is_empty(&thread1
.free_list
)) {
367 pt
= get_struct(thread1
.free_list
.prev
, struct per_thread
, free_list
);
368 list_del(&pt
->free_list
);
369 mutex_unlock(&thread1
.mutex
);
372 mutex_unlock(&thread1
.mutex
);
373 pt
= mem_align_mayfail(struct per_thread
*, round_up(sizeof(struct per_thread
), SMP_ALIAS_ALIGNMENT
), SMP_ALIAS_ALIGNMENT
, &sink
);
374 if (likely(pt
!= NULL
)) {
375 mutex_init(&pt
->mutex
);
376 mem_per_thread_init(pt
);
378 mutex_lock(&thread1
.mutex
);
379 list_add(&thread1
.used_list
, &pt
->used_list
);
380 mutex_unlock(&thread1
.mutex
);
381 tls_set(struct per_thread
*, mem_per_thread
, pt
);
382 tls_destructor(&pt
->destructor
, mem_per_thread_destructor
);
384 tls_set(struct per_thread
*, mem_per_thread
, NULL
);
390 static struct per_thread
*mem_current_thread(void)
392 struct per_thread
*pt
;
393 if (unlikely(!memory_threads_initialized
)) {
396 pt
= tls_get(struct per_thread
*, mem_per_thread
);
398 pt
= mem_per_thread_alloc();
406 static struct per_thread
*mem_mutex_lock(struct alloc_header attr_unused
*ah
)
408 struct per_thread
*pt
;
414 if (likely(memory_threads_initialized
)) {
415 mutex_lock(&pt
->mutex
);
417 ajla_assert(pt
== ah
->per_thread
, (file_line
, "mem_mutex_lock: per_thread changed: %p != %p", pt
, ah
->per_thread
));
423 static void mem_mutex_unlock(struct per_thread
*pt
)
425 if (likely(memory_threads_initialized
)) {
426 mutex_unlock(&pt
->mutex
);
430 #define VFY_UNALIGNED 1
431 #define VFY_ALIGNED 2
434 #ifdef POINTER_IGNORE_START
435 #define verify_no_tag(ah, position, fn) \
437 if (unlikely((ptr_to_num(ah) & POINTER_IGNORE_MASK) != 0))\
438 internal(position_string(position), "%s: pointer is tagged: %p", fn, AH_DATA(ah));\
441 #define verify_no_tag(ah, position, fn) \
446 #define verify_block(ah, aligned, position, fn) \
448 verify_no_tag(ah, position, fn); \
450 ((aligned) & VFY_UNALIGNED && likely(AH_MAGIC(ah) == ALLOC_MAGIC)) ||\
451 ((aligned) & VFY_ALIGNED && likely(AH_MAGIC(ah) == ALLOC_MAGIC_ALIGNED))\
453 internal(position_string(position), "%s: magic doesn't match: %08lx", fn, (unsigned long)AH_MAGIC(ah));\
454 if (USE_RED_ZONE && unlikely(AH_RED_ZONE(ah) != RED_ZONE)) \
455 internal(position_string(position), "%s: red zone damaged: %02x (block allocated at %s)", fn, AH_RED_ZONE(ah), position_string(ah->position));\
458 static size_t get_needed_size(size_t size
, size_t extra
)
460 size_t needed_size
= size
+ AH_SIZE
+ USE_RED_ZONE
;
461 if (unlikely(needed_size
< size
))
462 fatal("allocation size overflow");
463 needed_size
+= extra
;
464 if (unlikely(needed_size
< extra
))
465 fatal("allocation size overflow");
469 static attr_noinline
void *debug_mem_alloc(size_t size
, size_t alignment
, bool aligned
, bool clear
, ajla_error_t
*mayfail
, position_t position
)
471 unsigned char *result
;
473 struct alloc_header
*ah
;
475 if (unlikely(!is_power_of_2(alignment
)))
476 internal(position_string(position
), "debug_mem_alloc: invalid alignment %"PRIuMAX
", size %"PRIuMAX
"", (uintmax_t)alignment
, (uintmax_t)size
);
477 needed_size
= get_needed_size(size
, alignment
- 1);
478 result
= cast_cpp(unsigned char *, alloc_should_fail(mayfail
) ? NULL
: !clear
? heap_malloc(needed_size
) : heap_calloc(needed_size
));
479 if (unlikely(!result
)) {
480 result
= cast_cpp(unsigned char *, oom_calloc(needed_size
, mayfail
, position
));
484 padding
= -(size_t)ptr_to_num(result
+ AH_SIZE
) & (alignment
- 1);
485 ah
= cast_ptr(struct alloc_header
*, result
+ padding
);
486 ah
->padding
= padding
;
487 ah
->position
= position
;
489 if (USE_FILL
&& !clear
)
490 (void)memset(AH_DATA(ah
), get_memory_fill(), size
);
491 AH_MAGIC(ah
) = !aligned
? ALLOC_MAGIC
: ALLOC_MAGIC_ALIGNED
;
493 AH_RED_ZONE(ah
) = RED_ZONE
;
495 ah
->per_thread
= mem_current_thread();
497 if (USE_LIST
| USE_HISTOGRAM
) {
498 struct per_thread
*pt
;
499 pt
= mem_mutex_lock(ah
);
501 list_add(&pt
->block_list
, &ah
->entry
);
502 if (unlikely(pt
->bytes
+ ah
->size
< pt
->bytes
) || unlikely(!(pt
->blocks
+ 1)))
503 internal(file_line
, "debug_mem_alloc: memory counters overflow: %"PRIuMAX
", %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)pt
->bytes
, (uintmax_t)ah
->size
, (uintmax_t)pt
->blocks
);
504 pt
->bytes
+= ah
->size
;
506 /*debug("size: %lu, amount: %lu, blocks: %lu", ah->size, memory_amount, memory_blocks);*/
509 increment_histogram(pt
, ah
->size
, 1, ah
->position
);
510 mem_mutex_unlock(pt
);
515 static attr_noinline
void *debug_mem_realloc(void *ptr
, size_t size
, ajla_error_t
*mayfail
, position_t position
)
517 size_t needed_size
, padding
;
518 unsigned char *result
;
519 struct alloc_header
*new_ah
;
520 struct alloc_header
*ah
;
521 struct per_thread
*pt
;
524 internal(position_string(position
), "debug_mem_realloc(NULL, %"PRIuMAX
")", (uintmax_t)size
);
526 ah
= AH_FROM_PTR(ptr
);
527 verify_block(ah
, VFY_UNALIGNED
, position
, "debug_mem_realloc");
528 if (USE_FILL
&& size
< ah
->size
)
529 (void)memset(AH_DATA(ah
) + size
, get_memory_fill(), ah
->size
- size
);
530 pt
= mem_mutex_lock(ah
);
531 AH_MAGIC(ah
) = ALLOC_MAGIC_REALLOC
;
532 padding
= ah
->padding
;
533 needed_size
= get_needed_size(size
, padding
);
534 result
= cast_cpp(unsigned char *, alloc_should_fail(mayfail
) ? NULL
: heap_realloc(AH_MALLOC_BLOCK(ah
), needed_size
));
535 if (unlikely(!result
)) {
536 AH_MAGIC(ah
) = ALLOC_MAGIC
;
537 mem_mutex_unlock(pt
);
538 result
= cast_cpp(unsigned char *, oom_calloc(needed_size
, mayfail
, position
));
540 if (size
<= ah
->size
) {
543 AH_RED_ZONE(ah
) = RED_ZONE
;
548 pt
= mem_mutex_lock(ah
);
549 (void)memcpy(result
+ padding
, ah
, minimum(size
, ah
->size
) + AH_SIZE
);
550 AH_MAGIC(ah
) = ALLOC_MAGIC_REALLOC
;
553 new_ah
= cast_ptr(struct alloc_header
*, result
+ padding
);
554 AH_MAGIC(new_ah
) = ALLOC_MAGIC
;
556 new_ah
->entry
.next
->prev
= &new_ah
->entry
;
557 new_ah
->entry
.prev
->next
= &new_ah
->entry
;
558 if (unlikely(pt
->bytes
< new_ah
->size
) || unlikely(!pt
->blocks
))
559 internal(file_line
, "debug_mem_realloc: memory counters underflow: %"PRIuMAX
", %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)pt
->bytes
, (uintmax_t)new_ah
->size
, (uintmax_t)pt
->blocks
);
560 pt
->bytes
-= new_ah
->size
;
561 if (unlikely(pt
->bytes
+ size
< pt
->bytes
))
562 internal(file_line
, "debug_mem_realloc: memory counters overflow: %"PRIuMAX
", %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)pt
->bytes
, (uintmax_t)size
, (uintmax_t)pt
->blocks
);
567 AH_RED_ZONE(new_ah
) = RED_ZONE
;
569 increment_histogram(pt
, size
, 1, new_ah
->position
);
570 mem_mutex_unlock(pt
);
571 return AH_DATA(new_ah
);
574 static attr_noinline
void debug_mem_free(const void *ptr
, unsigned vfy
, position_t position
)
576 struct alloc_header
*ah
;
579 internal(position_string(position
), "debug_mem_free(NULL)");
581 ah
= AH_FROM_PTR(ptr
);
582 verify_block(ah
, vfy
, position
, "debug_mem_free");
583 if (USE_FILL
&& (!amalloc_enabled
|| !aptr_is_huge(AH_MALLOC_BLOCK(ah
)))) {
584 unsigned char mf
= get_memory_fill();
585 unsigned char *zero_p
= AH_DATA(ah
);
586 size_t zero_size
= ah
->size
;
587 if (zero_size
> sizeof(refcount_t
) && (int8_t)mf
>= -0x70) {
588 zero_p
+= sizeof(refcount_t
);
589 zero_size
-= sizeof(refcount_t
);
590 #ifndef DEBUG_REFCOUNTS
591 refcount_init((refcount_t
*)AH_DATA(ah
));
594 (void)memset(zero_p
, mf
, zero_size
);
596 if (USE_LIST
| USE_HISTOGRAM
) {
597 struct per_thread
*pt
;
598 pt
= mem_mutex_lock(ah
);
600 list_del(&ah
->entry
);
601 if (unlikely(pt
->bytes
< ah
->size
) || unlikely(!pt
->blocks
))
602 internal(file_line
, "debug_mem_free: memory counters underflow: %"PRIuMAX
", %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)pt
->bytes
, (uintmax_t)ah
->size
, (uintmax_t)pt
->blocks
);
603 pt
->bytes
-= ah
->size
;
606 mem_mutex_unlock(pt
);
608 AH_MAGIC(ah
) = ALLOC_MAGIC_FREE
;
609 heap_free(AH_MALLOC_BLOCK(ah
));
612 /* this should not be called concurrently */
613 static attr_noinline
void debug_mem_set_position(const void *ptr
, position_t position
)
615 struct alloc_header
*ah
;
618 internal(position_string(position
), "debug_mem_set_position(NULL)");
620 ah
= AH_FROM_PTR(ptr
);
621 verify_block(ah
, VFY_ANY
, position
, "debug_mem_set_position");
623 ah
->position
= position
;
626 static attr_noinline
const char *debug_mem_get_position(const void *ptr
, position_t position
)
628 struct alloc_header
*ah
;
631 internal(position_string(position
), "debug_mem_get_position(NULL)");
633 ah
= AH_FROM_PTR(ptr
);
634 verify_block(ah
, VFY_ANY
, position
, "debug_mem_get_position");
636 return position_string(ah
->position
);
639 static attr_noinline
void debug_mem_verify(const void *ptr
, position_t position
)
641 struct alloc_header
*ah
;
642 ah
= AH_FROM_PTR(ptr
);
643 verify_block(ah
, VFY_UNALIGNED
, position
, "debug_mem_verify");
646 static attr_noinline
void debug_mem_verify_aligned(const void *ptr
, position_t position
)
648 struct alloc_header
*ah
;
649 ah
= AH_FROM_PTR(ptr
);
650 verify_block(ah
, VFY_ALIGNED
, position
, "debug_mem_verify_aligned");
655 #define verify_size \
657 if (sizeof(ptrdiff_t) < 8 && \
658 (unlikely(size != (size_t)(ptrdiff_t)size) || \
659 unlikely((ptrdiff_t)size < 0))) { \
660 fatal_mayfail(error_ajla(EC_ASYNC, AJLA_ERROR_SIZE_OVERFLOW), mayfail, "allocation size overflow: %"PRIuMAX" bytes", (uintmax_t)size);\
665 void * attr_hot_fastcall
mem_alloc_position(size_t size
, ajla_error_t
*mayfail argument_position
)
668 if (likely(mayfail
!= MEM_DONT_TRY_TO_FREE
))
669 address_lock_verify();
671 #ifdef DEBUG_MEMORY_POSSIBLE
672 if (unlikely(memory_debug
))
673 return debug_mem_alloc(size
, 1, false, false, mayfail
, position_arg
);
675 new_ptr
= alloc_should_fail(mayfail
) ? NULL
: heap_malloc(size
);
676 if (unlikely(!new_ptr
)) {
677 new_ptr
= oom_calloc(size
, mayfail
, position_arg
);
682 void * attr_hot_fastcall
mem_calloc_position(size_t size
, ajla_error_t
*mayfail argument_position
)
685 if (likely(mayfail
!= MEM_DONT_TRY_TO_FREE
))
686 address_lock_verify();
688 #ifdef DEBUG_MEMORY_POSSIBLE
689 if (unlikely(memory_debug
))
690 return debug_mem_alloc(size
, 1, false, true, mayfail
, position_arg
);
692 new_ptr
= alloc_should_fail(mayfail
) ? NULL
: heap_calloc(size
);
694 new_ptr
= oom_calloc(size
, mayfail
, position_arg
);
700 void * attr_hot_fastcall
mem_align_position(size_t size
, size_t alignment
, ajla_error_t
*mayfail argument_position
)
703 if (likely(mayfail
!= MEM_DONT_TRY_TO_FREE
))
704 address_lock_verify();
706 #ifdef DEBUG_MEMORY_POSSIBLE
707 if (unlikely(memory_debug
))
708 return debug_mem_alloc(size
, alignment
, true, false, mayfail
, position_arg
);
710 new_ptr
= alloc_should_fail(mayfail
) ? NULL
: heap_memalign(alignment
, size
);
711 if (unlikely(!new_ptr
)) {
712 new_ptr
= oom_cmemalign(size
, alignment
, mayfail
, position_arg
);
717 void * attr_hot_fastcall
mem_calign_position(size_t size
, size_t alignment
, ajla_error_t
*mayfail argument_position
)
720 if (likely(mayfail
!= MEM_DONT_TRY_TO_FREE
))
721 address_lock_verify();
723 #ifdef DEBUG_MEMORY_POSSIBLE
724 if (unlikely(memory_debug
))
725 return debug_mem_alloc(size
, alignment
, true, true, mayfail
, position_arg
);
727 new_ptr
= alloc_should_fail(mayfail
) ? NULL
: heap_cmemalign(alignment
, size
);
728 if (unlikely(!new_ptr
)) {
729 new_ptr
= oom_cmemalign(size
, alignment
, mayfail
, position_arg
);
734 void * attr_hot_fastcall
mem_realloc_position(void *ptr
, size_t size
, ajla_error_t
*mayfail argument_position
)
737 if (likely(mayfail
!= MEM_DONT_TRY_TO_FREE
))
738 address_lock_verify();
740 #ifdef DEBUG_MEMORY_POSSIBLE
741 if (unlikely(memory_debug
))
742 return debug_mem_realloc(ptr
, size
, mayfail
, position_arg
);
744 if (unlikely(!size
)) {
745 new_ptr
= mem_alloc_position(0, mayfail pass_position
);
746 if (likely(new_ptr
!= NULL
))
747 mem_free_position(ptr pass_position
);
750 new_ptr
= alloc_should_fail(mayfail
) ? NULL
: heap_realloc(ptr
, size
);
752 new_ptr
= oom_realloc(ptr
, size
, mayfail
, position_arg
);
757 void attr_hot_fastcall
mem_free_position(const void *ptr argument_position
)
759 #ifdef DEBUG_MEMORY_POSSIBLE
760 if (unlikely(memory_debug
)) {
761 debug_mem_free(ptr
, VFY_UNALIGNED
, position_arg
);
765 heap_free((void *)ptr
);
768 void attr_hot_fastcall
mem_free_aligned_position(const void *ptr argument_position
)
770 #ifdef DEBUG_MEMORY_POSSIBLE
771 if (unlikely(memory_debug
)) {
772 debug_mem_free(ptr
, VFY_ALIGNED
, position_arg
);
776 heap_free_aligned((void *)ptr
);
779 #ifdef DEBUG_MEMORY_POSSIBLE
780 void attr_fastcall
mem_set_position(const void *ptr argument_position
)
782 if (unlikely(memory_debug
)) {
783 debug_mem_set_position(ptr
, position_arg
);
786 const char * attr_fastcall
mem_get_position(const void *ptr argument_position
)
788 if (unlikely(memory_debug
)) {
789 return debug_mem_get_position(ptr
, position_arg
);
791 return "unknown position";
793 void attr_fastcall
mem_verify_position(const void *ptr argument_position
)
795 if (unlikely(memory_debug
)) {
796 debug_mem_verify(ptr
, position_arg
);
799 void attr_fastcall
mem_verify_aligned_position(const void *ptr argument_position
)
801 if (unlikely(memory_debug
)) {
802 debug_mem_verify_aligned(ptr
, position_arg
);
807 bool attr_cold
mem_trim_cache(void)
813 #ifdef DEBUG_MEMORY_POSSIBLE
814 static mutex_t mem_report_mutex
;
816 struct memory_entry
{
819 uintptr_t cumulative_size
;
823 static bool attr_cold
add_memory_entry(struct memory_entry
**me
, size_t *me_l
, struct alloc_header
*ah
)
825 if (unlikely(!(*me_l
& (*me_l
- 1)))) {
826 struct memory_entry
*m
;
827 size_t ns
= !*me_l
? 1 : *me_l
* 2;
828 if (unlikely(!ns
) || ns
> (size_t)-1 / sizeof(struct alloc_header
))
830 m
= heap_realloc(*me
, ns
* sizeof(struct alloc_header
));
835 (*me
)[*me_l
].position
= ah
->position
;
836 (*me
)[*me_l
].size
= ah
->size
;
837 (*me
)[*me_l
].cumulative_size
= ah
->size
;
838 (*me
)[*me_l
].n_blocks
= 1;
843 static bool attr_cold
add_memory_entries(struct memory_entry
**me
, size_t *me_l
, struct per_thread
*pt
)
846 list_for_each(l
, &pt
->block_list
) {
847 struct alloc_header
*ah
= get_struct(l
, struct alloc_header
, entry
);
848 if (unlikely(!add_memory_entry(me
, me_l
, ah
)))
854 static int attr_cold
mem_compare_file_line(const void *me1_
, const void *me2_
)
856 const struct memory_entry
*me1
= me1_
;
857 const struct memory_entry
*me2
= me2_
;
858 const char *p1
= position_string_alloc(me1
->position
);
859 const char *p2
= position_string_alloc(me2
->position
);
860 #ifdef HAVE_STRVERSCMP
861 int c
= strverscmp(p1
, p2
);
863 int c
= strcmp(p1
, p2
);
865 position_string_free(p1
);
866 position_string_free(p2
);
870 static int attr_cold
mem_compare_cumulative_size(const void *me1_
, const void *me2_
)
872 const struct memory_entry
*me1
= me1_
;
873 const struct memory_entry
*me2
= me2_
;
874 if (me1
->cumulative_size
< me2
->cumulative_size
)
876 if (me1
->cumulative_size
> me2
->cumulative_size
)
878 if (me1
->n_blocks
< me2
->n_blocks
)
880 if (me1
->n_blocks
> me2
->n_blocks
)
882 return mem_compare_file_line(me1
, me2
);
885 void attr_cold
mem_report_usage(int mode
, const char *string
)
887 struct memory_entry
*me
;
888 size_t me_l
, me_l2
, mr
;
894 size_t max_ps
, max_digits
;
897 warning("memory list not available, use --debug=leak");
901 if (memory_threads_initialized
) mutex_lock(&mem_report_mutex
);
908 if (memory_threads_initialized
) mutex_lock(&thread1
.mutex
);
909 ok
= add_memory_entries(&me
, &me_l
, &thread1
);
911 list_for_each(l
, &thread1
.used_list
) {
912 struct per_thread
*pt
= get_struct(l
, struct per_thread
, used_list
);
913 if (memory_threads_initialized
) mutex_lock(&pt
->mutex
);
914 if (ok
) ok
= add_memory_entries(&me
, &me_l
, pt
);
915 if (memory_threads_initialized
) mutex_unlock(&pt
->mutex
);
917 list_for_each(l
, &thread1
.free_list
) {
918 struct per_thread
*pt
= get_struct(l
, struct per_thread
, free_list
);
919 if (memory_threads_initialized
) mutex_lock(&pt
->mutex
);
920 if (ok
) ok
= add_memory_entries(&me
, &me_l
, pt
);
921 if (memory_threads_initialized
) mutex_unlock(&pt
->mutex
);
924 if (memory_threads_initialized
) mutex_unlock(&thread1
.mutex
);
929 for (mr
= 0; mr
< me_l
; mr
++)
930 total
+= me
[mr
].cumulative_size
;
932 debug("allocated memory%s%s: %"PRIuMAX
" / %"PRIuMAX
" = %"PRIuMAX
"", *string
? " at " : "", string
, (uintmax_t)total
, (uintmax_t)me_l
, (uintmax_t)(total
/ (me_l
? me_l
: 1)));
934 if (mode
== MR_SUMMARY
) {
936 } else if (mode
== MR_MOST_ALLOCATED
) {
937 qsort(me
, me_l
, sizeof(struct memory_entry
), mem_compare_file_line
);
939 for (mr
= 0; mr
< me_l
; mr
++) {
941 while (mr
+ 1 < me_l
&& !mem_compare_file_line(&me
[mr
], &me
[mr
+ 1])) {
943 me
[me_l2
].cumulative_size
+= me
[mr
].size
;
944 me
[me_l2
].n_blocks
++;
948 } else if (mode
== MR_LARGEST_BLOCKS
) {
951 internal(file_line
, "mem_report_usage: invalid mode %d", mode
);
953 qsort(me
, me_l2
, sizeof(struct memory_entry
), mem_compare_cumulative_size
);
956 for (mr
= 0; mr
< me_l2
; mr
++) {
957 const char *ps
= position_string_alloc(me
[mr
].position
);
958 size_t psl
= strlen(ps
);
959 position_string_free(ps
);
964 char *max_str
= str_from_unsigned(me
[0].cumulative_size
, 10);
965 max_digits
= strlen(max_str
);
971 for (mr
= 0; mr
< me_l2
; mr
++) {
976 ps
= position_string_alloc(me
[mr
].position
);
977 str_add_string(&s
, &sl
, ps
);
978 position_string_free(ps
);
979 ps
= str_from_unsigned(me
[mr
].cumulative_size
, 10);
981 while (sl
< max_ps
+ 1 + (max_digits
- psl
))
982 str_add_char(&s
, &sl
, ' ');
983 str_add_string(&s
, &sl
, ps
);
985 if (mode
== MR_MOST_ALLOCATED
) {
986 str_add_bytes(&s
, &sl
, " / ", 3);
987 str_add_unsigned(&s
, &sl
, me
[mr
].n_blocks
, 10);
988 str_add_bytes(&s
, &sl
, " = ", 3);
989 str_add_unsigned(&s
, &sl
, me
[mr
].cumulative_size
/ me
[mr
].n_blocks
, 10);
990 } else if (mode
== MR_LARGEST_BLOCKS
) {
992 for (mq
= mr
+ 1; mq
< me_l2
; mq
++) {
993 if (mem_compare_file_line(&me
[mr
], &me
[mq
]))
995 if (me
[mr
].cumulative_size
!= me
[mq
].cumulative_size
)
999 str_add_bytes(&s
, &sl
, " x ", 3);
1000 str_add_unsigned(&s
, &sl
, mq
- mr
, 10);
1004 str_finish(&s
, &sl
);
1010 if (memory_threads_initialized
) mutex_unlock(&mem_report_mutex
);
1015 if (memory_threads_initialized
) mutex_unlock(&mem_report_mutex
);
1016 if (me
) heap_free(me
);
1017 warning("out of memory for memory list, allocated size %"PRIuMAX
"", (uintmax_t)me_l
);
1021 #ifdef DEBUG_MEMORY_POSSIBLE
1022 static attr_noreturn attr_cold
mem_dump_leaks(void)
1024 struct list leaked_list
;
1028 const char *head
= "memory leak: ";
1029 size_t strlen_head
= strlen(head
);
1030 const char *first_pos
= file_line
;
1031 uintmax_t n_blocks
= 0;
1032 uintmax_t n_bytes
= 0;
1034 list_take(&leaked_list
, &thread1
.block_list
);
1037 list_for_each_back(lv
, &leaked_list
) {
1038 struct alloc_header
*ah
;
1039 const char *pos_str
;
1043 ah
= get_struct(lv
, struct alloc_header
, entry
);
1044 pos_str
= position_string(ah
->position
);
1047 str_add_unsigned(&t
, &tl
, ptr_to_num((char *)ah
+ AH_SIZE
), 16);
1048 str_add_string(&t
, &tl
, ":");
1049 str_add_unsigned(&t
, &tl
, ah
->size
, 10);
1050 str_add_string(&t
, &tl
, " @ ");
1051 str_add_string(&t
, &tl
, pos_str
);
1052 str_finish(&t
, &tl
);
1054 if (sl
&& strlen_head
+ sl
+ 2 + tl
> 174 - 15) {
1055 str_finish(&s
, &sl
);
1056 debug("memory leak: %s", s
);
1061 if (sl
) str_add_string(&s
, &sl
, ", ");
1062 else first_pos
= pos_str
;
1063 str_add_string(&s
, &sl
, t
);
1067 n_bytes
+= ah
->size
;
1070 str_finish(&s
, &sl
);
1072 internal(first_pos
, "memory leak (%"PRIuMAX
" blocks, %"PRIuMAX
" bytes): %s", n_blocks
, n_bytes
, s
);
1076 bool mem_enable_debugging_option(const char *option
, size_t l
)
1078 #ifndef DEBUG_MEMORY_POSSIBLE
1079 int memory_debug
= 0;
1082 memory_debug
|= MEMORY_DEBUG_MAGIC
| MEMORY_DEBUG_REDZONE
| MEMORY_DEBUG_FILL
| MEMORY_DEBUG_TRACK_BLOCKS
;
1083 else if (l
== 5 && !strncmp(option
, "magic", l
))
1084 memory_debug
|= MEMORY_DEBUG_MAGIC
;
1085 else if (l
== 7 && !strncmp(option
, "redzone", l
))
1086 memory_debug
|= MEMORY_DEBUG_REDZONE
;
1087 else if (l
== 4 && !strncmp(option
, "fill", l
))
1088 memory_debug
|= MEMORY_DEBUG_FILL
;
1089 else if (l
== 4 && !strncmp(option
, "leak", l
))
1090 memory_debug
|= MEMORY_DEBUG_TRACK_BLOCKS
;
1091 else if (l
== 6 && !strncmp(option
, "memory", l
))
1092 memory_debug
|= MEMORY_DEBUG_MAGIC
| MEMORY_DEBUG_REDZONE
| MEMORY_DEBUG_FILL
| MEMORY_DEBUG_TRACK_BLOCKS
;
1098 bool mem_al_enable_profile(const char *option
, size_t l
)
1100 #ifndef DEBUG_MEMORY_POSSIBLE
1101 int memory_debug
= 0;
1104 memory_debug
|= MEMORY_DEBUG_HISTOGRAM
;
1105 else if (l
== 6 && !strncmp(option
, "memory", l
))
1106 memory_debug
|= MEMORY_DEBUG_HISTOGRAM
;
1112 void mem_al_set_ptrcomp(const char attr_unused
*str
)
1114 #ifdef POINTER_COMPRESSION_POSSIBLE
1115 pointer_compression_enabled
= 1;
1119 void mem_al_set_system_malloc(const char attr_unused
*str
)
1122 amalloc_enabled
= 0;
1128 #if defined(POINTER_COMPRESSION_POSSIBLE) && defined(USE_AMALLOC)
1129 if (pointer_compression_enabled
&& !amalloc_enabled
)
1130 fatal("The options --ptrcomp and --system-malloc are not compatible");
1132 #ifdef DEBUG_MEMORY_POSSIBLE
1133 if (USE_LIST
| USE_HISTOGRAM
) {
1134 mem_per_thread_init(&thread1
);
1136 memory_threads_initialized
= false;
1137 /*if (memory_debug & MEMORY_DEBUG_REDZONE && dl_sym("EF_Abort", NULL)) {
1138 debug("Electric Fence detected, disabling red zone");
1139 memory_debug &= ~MEMORY_DEBUG_REDZONE;
1142 list_init(&thread1
.used_list
);
1147 void mem_init_multithreaded(void)
1149 #ifdef DEBUG_MEMORY_POSSIBLE
1150 if (unlikely(memory_threads_initialized
))
1151 internal(file_line
, "mem_init_multithreaded: memory_threads_initialized already set");
1152 if (USE_LIST
| USE_HISTOGRAM
) {
1153 mutex_init(&thread1
.mutex
);
1154 mutex_init(&mem_report_mutex
);
1155 tls_init(unsigned char, memory_fill
);
1157 list_init(&thread1
.free_list
);
1158 tls_init(struct per_thread
*, mem_per_thread
);
1159 tls_set(struct per_thread
*, mem_per_thread
, &thread1
);
1161 memory_threads_initialized
= true;
1166 void mem_done_multithreaded(void)
1168 #ifdef DEBUG_MEMORY_POSSIBLE
1169 if (unlikely(!!memory_threads_initialized
!= !!(USE_LIST
| USE_HISTOGRAM
)))
1170 internal(file_line
, "mem_done_multithreaded: memory_threads_initialized %sset", memory_threads_initialized
? "" : "not ");
1171 if (USE_LIST
| USE_HISTOGRAM
) {
1172 memory_threads_initialized
= false;
1174 tls_done(struct per_thread
*, mem_per_thread
);
1175 while (!list_is_empty(&thread1
.free_list
)) {
1176 struct per_thread
*pt
= get_struct(thread1
.free_list
.next
, struct per_thread
, free_list
);
1177 list_del(&pt
->free_list
);
1178 mem_per_thread_free(pt
);
1179 /* { static unsigned x = 0; debug("freeing per_thread: %u", ++x); } */
1181 if (!list_is_empty(&thread1
.used_list
)) {
1182 internal(file_line
, "mem_done_multithreaded: used_list is not empty");
1185 tls_done(unsigned char, memory_fill
);
1186 mutex_done(&mem_report_mutex
);
1187 mutex_done(&thread1
.mutex
);
1194 #ifdef DEBUG_MEMORY_POSSIBLE
1195 if (unlikely(memory_threads_initialized
))
1196 internal(file_line
, "mem_done: memory_threads_initialized set");
1198 if (unlikely(!list_is_empty(&thread1
.block_list
)))
1200 if (unlikely(thread1
.bytes
!= 0) || unlikely(thread1
.blocks
!= 0))
1201 internal(file_line
, "mem_done: memory counters leaked: %"PRIuMAX
", %"PRIuMAX
"", (uintmax_t)thread1
.bytes
, (uintmax_t)thread1
.blocks
);
1203 if (USE_HISTOGRAM
) {
1205 for (i
= 0; i
< thread1
.histogram_size
; i
++)
1206 if (unlikely(thread1
.histogram
[i
].cnt
!= 0))
1207 debug("%"PRIuMAX
"(%"PRIxMAX
") : %"PRIuMAX
"\t\t%s", (uintmax_t)i
, (uintmax_t)i
, thread1
.histogram
[i
].cnt
, position_string(thread1
.histogram
[i
].position
));
1208 heap_free(thread1
.histogram
);
1209 thread1
.histogram
= NULL
;