50#define LOG_MODULE "HeapMem"
51#define LOG_LEVEL LOG_LEVEL_WARN
55#ifdef HEAPMEM_CONF_PRINTF
56#define HEAPMEM_PRINTF(...) HEAPMEM_CONF_PRINTF(__VA_ARGS__)
58#define HEAPMEM_PRINTF(...) LOG_OUTPUT(__VA_ARGS__)
67#ifdef HEAPMEM_CONF_SEARCH_MAX
68#define CHUNK_SEARCH_MAX HEAPMEM_CONF_SEARCH_MAX
70#define CHUNK_SEARCH_MAX 16
73_Static_assert((HEAPMEM_ALIGNMENT & (HEAPMEM_ALIGNMENT - 1)) == 0,
74 "HEAPMEM_ALIGNMENT must be a power of 2");
77align_size(
size_t size)
79 if(size > SIZE_MAX - (HEAPMEM_ALIGNMENT - 1)) {
82 return ((size + (HEAPMEM_ALIGNMENT - 1)) & ~(HEAPMEM_ALIGNMENT - 1));
85#define ALIGN(size) align_size(size)
88#define NEXT_CHUNK(chunk) \
89 ((chunk_t *)((char *)(chunk) + sizeof(chunk_t) + (chunk)->size))
90#define IS_LAST_CHUNK(zone, chunk) \
91 ((char *)NEXT_CHUNK(chunk) == (zone)->heap_base + (zone)->heap_usage)
95#define GET_CHUNK(ptr) \
96 ((chunk_t *)((char *)(ptr) - sizeof(chunk_t)))
97#define GET_PTR(chunk) \
105typedef struct heapmem_chunk {
106 struct heapmem_chunk *prev;
107 struct heapmem_chunk *next;
116_Static_assert(
sizeof(chunk_t) % HEAPMEM_ALIGNMENT == 0,
117 "sizeof(chunk_t) must be a multiple of HEAPMEM_ALIGNMENT");
120#ifdef HEAPMEM_CONF_ARENA_SIZE
121#define HEAPMEM_ARENA_SIZE HEAPMEM_CONF_ARENA_SIZE
122static char heapmem_general_buf_[HEAPMEM_ARENA_SIZE] CC_ALIGN(HEAPMEM_ALIGNMENT);
123static heapmem_zone_t heapmem_zone_general = {
125 .heap_base = heapmem_general_buf_,
126 .arena_size = HEAPMEM_ARENA_SIZE,
135static inline heapmem_zone_t *
136resolve_zone(heapmem_zone_t *zone)
138#ifdef HEAPMEM_CONF_ARENA_SIZE
140 return &heapmem_zone_general;
146#define IN_ZONE(zone, ptr) ((ptr) != NULL && \
147 (char *)(ptr) >= (zone)->heap_base + sizeof(chunk_t) && \
148 (char *)(ptr) < (zone)->heap_base + (zone)->heap_usage)
153extend_space(heapmem_zone_t *zone,
size_t size)
155 if(size > zone->arena_size - zone->heap_usage) {
159 char *old_usage = zone->heap_base + zone->heap_usage;
160 zone->heap_usage += size;
161 if(zone->heap_usage > zone->max_heap_usage) {
162 zone->max_heap_usage = zone->heap_usage;
170free_chunk(heapmem_zone_t *zone, chunk_t *
const chunk)
172 chunk->allocated =
false;
174 if(IS_LAST_CHUNK(zone, chunk)) {
176 zone->heap_usage -=
sizeof(chunk_t) + chunk->size;
180 chunk->next = zone->free_list;
181 if(zone->free_list != NULL) {
182 zone->free_list->prev = chunk;
184 zone->free_list = chunk;
191remove_chunk_from_free_list(heapmem_zone_t *zone, chunk_t *
const chunk)
193 if(chunk == zone->free_list) {
194 zone->free_list = chunk->next;
195 if(zone->free_list != NULL) {
196 zone->free_list->prev = NULL;
199 chunk->prev->next = chunk->next;
202 if(chunk->next != NULL) {
203 chunk->next->prev = chunk->prev;
213split_chunk(heapmem_zone_t *zone, chunk_t *
const chunk,
size_t offset)
215 offset = ALIGN(offset);
217 if(offset +
sizeof(chunk_t) < chunk->size) {
218 chunk_t *new_chunk = (chunk_t *)(GET_PTR(chunk) + offset);
219 new_chunk->size = chunk->size -
sizeof(chunk_t) - offset;
220 new_chunk->allocated =
false;
221 free_chunk(zone, new_chunk);
223 chunk->size = offset;
224 chunk->next = chunk->prev = NULL;
231coalesce_chunks(heapmem_zone_t *zone, chunk_t *chunk)
233 for(chunk_t *next = NEXT_CHUNK(chunk);
234 (
char *)next < zone->heap_base + zone->heap_usage && !next->allocated;
235 next = NEXT_CHUNK(next)) {
236 chunk->size +=
sizeof(chunk_t) + next->size;
237 LOG_DBG(
"Coalesce chunk of %zu bytes\n", next->size);
238 remove_chunk_from_free_list(zone, next);
245defrag_chunks(heapmem_zone_t *zone)
248 int i = CHUNK_SEARCH_MAX;
249 for(chunk_t *chunk = zone->free_list; chunk != NULL; chunk = chunk->next) {
253 coalesce_chunks(zone, chunk);
260get_free_chunk(heapmem_zone_t *zone,
const size_t size)
265 chunk_t *best = NULL;
267 int i = CHUNK_SEARCH_MAX;
268 for(chunk_t *chunk = zone->free_list; chunk != NULL; chunk = chunk->next) {
275 if(size <= chunk->size) {
276 if(best == NULL || chunk->size < best->size) {
279 if(best->size == size) {
289 remove_chunk_from_free_list(zone, best);
290 split_chunk(zone, best, size);
312zone_alloc(heapmem_zone_t *zone,
size_t size,
313 const char *file,
const unsigned line)
315 zone = resolve_zone(zone);
316 if(zone == NULL || zone->heap_base == NULL) {
317 LOG_WARN(
"Attempt to allocate from invalid zone\n");
321 if(size > zone->arena_size || size == 0) {
327 LOG_ERR(
"Size overflow in alignment\n");
331 chunk_t *chunk = get_free_chunk(zone, size);
333 chunk = extend_space(zone,
sizeof(chunk_t) + size);
340 chunk->allocated =
true;
347 LOG_DBG(
"zone_alloc: zone \"%s\" ptr %p size %zu\n",
348 zone->name, GET_PTR(chunk), chunk->size);
350 return GET_PTR(chunk);
355heapmem_zone_alloc_debug(heapmem_zone_t *zone,
size_t size,
356 const char *file,
const unsigned line)
358 return zone_alloc(zone, size, file, line);
364 return zone_alloc(zone, size, NULL, 0);
380zone_free(heapmem_zone_t *zone,
void *ptr,
381 const char *file,
const unsigned line)
383 zone = resolve_zone(zone);
384 if(zone == NULL || !IN_ZONE(zone, ptr)) {
386 LOG_WARN(
"zone_free: ptr %p is not in the zone\n", ptr);
391 chunk_t *chunk = GET_CHUNK(ptr);
392 if(!chunk->allocated) {
393 LOG_WARN(
"zone_free: ptr %p has already been deallocated\n", ptr);
398 LOG_DBG(
"zone_free: ptr %p, allocated at %s:%u\n", ptr,
399 chunk->file, chunk->line);
402 free_chunk(zone, chunk);
408heapmem_zone_free_debug(heapmem_zone_t *zone,
void *ptr,
409 const char *file,
const unsigned line)
411 return zone_free(zone, ptr, file, line);
417 return zone_free(zone, ptr, NULL, 0);
439zone_realloc(heapmem_zone_t *zone,
void *ptr,
size_t size,
440 const char *file,
const unsigned line)
442 zone = resolve_zone(zone);
447 LOG_WARN(
"Attempt to use invalid zone\n");
450 if(ptr != NULL && !IN_ZONE(zone, ptr)) {
451 LOG_WARN(
"zone_realloc: ptr %p is not in the zone\n", ptr);
456 LOG_DBG(
"zone_realloc: ptr %p size %zu at %s:%u\n",
457 ptr, size, file, line);
461 if(size > zone->arena_size) {
467 return zone_alloc(zone, size, file, line);
468 }
else if(size == 0) {
469 zone_free(zone, ptr, file, line);
473 chunk_t *chunk = GET_CHUNK(ptr);
474 if(!chunk->allocated) {
475 LOG_WARN(
"zone_realloc: ptr %p is not allocated\n", ptr);
486 LOG_ERR(
"Size overflow in alignment\n");
490 size_t old_size = chunk->size;
492 if(size <= old_size) {
495 split_chunk(zone, chunk, size);
500 size_t size_increase = size - old_size;
502 if(IS_LAST_CHUNK(zone, chunk)) {
508 if(extend_space(zone, size_increase) != NULL) {
518 coalesce_chunks(zone, chunk);
519 if(chunk->size >= size) {
522 split_chunk(zone, chunk, size);
533 void *newptr = zone_alloc(zone, size, file, line);
538 memcpy(newptr, ptr, old_size);
539 zone_free(zone, ptr, file, line);
546heapmem_zone_realloc_debug(heapmem_zone_t *zone,
void *ptr,
size_t size,
547 const char *file,
const unsigned line)
549 return zone_realloc(zone, ptr, size, file, line);
555 return zone_realloc(zone, ptr, size, NULL, 0);
560zone_calloc(heapmem_zone_t *zone,
size_t nmemb,
size_t size,
561 const char *file,
const unsigned line)
563 size_t total_size = nmemb * size;
566 if(size == 0 || total_size / size != nmemb) {
570 void *ptr = zone_alloc(zone, total_size, file, line);
572 memset(ptr, 0, total_size);
579heapmem_zone_calloc_debug(heapmem_zone_t *zone,
size_t nmemb,
size_t size,
580 const char *file,
const unsigned line)
582 return zone_calloc(zone, nmemb, size, file, line);
588 return zone_calloc(zone, nmemb, size, NULL, 0);
596 zone = resolve_zone(zone);
597 memset(stats, 0,
sizeof(*stats));
602 for(chunk_t *chunk = (chunk_t *)zone->heap_base;
603 (
char *)chunk < zone->heap_base + zone->heap_usage;
604 chunk = NEXT_CHUNK(chunk)) {
605 stats->overhead +=
sizeof(chunk_t);
606 if(chunk->allocated) {
607 stats->allocated += chunk->size;
610 coalesce_chunks(zone, chunk);
611 stats->available += chunk->size;
614 stats->available += zone->arena_size - zone->heap_usage;
615 stats->heap_usage = zone->heap_usage;
616 stats->max_heap_usage = zone->max_heap_usage;
624 zone = resolve_zone(zone);
629 heapmem_stats_t stats;
632 HEAPMEM_PRINTF(
"* HeapMem zone \"%s\" statistics\n", zone->name);
633 HEAPMEM_PRINTF(
"* Arena size: %zu\n", zone->arena_size);
634 HEAPMEM_PRINTF(
"* Allocated memory: %zu\n", stats.allocated);
635 HEAPMEM_PRINTF(
"* Available memory: %zu\n", stats.available);
636 HEAPMEM_PRINTF(
"* Heap usage: %zu\n", stats.heap_usage);
637 HEAPMEM_PRINTF(
"* Max heap usage: %zu\n", stats.max_heap_usage);
638 HEAPMEM_PRINTF(
"* Allocated chunks: %zu\n", stats.chunks);
639 HEAPMEM_PRINTF(
"* Chunk size: %zu\n",
sizeof(chunk_t));
640 HEAPMEM_PRINTF(
"* Total chunk overhead: %zu\n", stats.overhead);
643 HEAPMEM_PRINTF(
"* Allocated chunks:\n");
644 for(chunk_t *chunk = (chunk_t *)zone->heap_base;
645 (
char *)chunk < zone->heap_base + zone->heap_usage;
646 chunk = NEXT_CHUNK(chunk)) {
647 if(chunk->allocated) {
649 HEAPMEM_PRINTF(
"* Chunk: heap offset %"PRIuPTR
", obj %p, size %zu (%s:%u)\n",
650 (uintptr_t)((
char *)chunk - zone->heap_base),
651 GET_PTR(chunk), chunk->size, chunk->file, chunk->line);
653 HEAPMEM_PRINTF(
"* Chunk: heap offset %"PRIuPTR
", obj %p, size %zu\n",
654 (uintptr_t)((
char *)chunk - zone->heap_base),
655 GET_PTR(chunk), chunk->size);
Default definitions of C compiler quirk work-arounds.
void * heapmem_zone_alloc(heapmem_zone_t *zone, size_t size)
Allocate a chunk of memory in the specified zone.
void heapmem_zone_stats(heapmem_zone_t *zone, heapmem_stats_t *stats)
Obtain internal statistics for a heapmem zone.
void * heapmem_zone_realloc(heapmem_zone_t *zone, void *ptr, size_t size)
Reallocate a chunk of memory in the specified zone.
void * heapmem_zone_calloc(heapmem_zone_t *zone, size_t nmemb, size_t size)
Allocate memory for a zero-initialized array in the specified zone.
void heapmem_zone_print_debug_info(heapmem_zone_t *zone, bool print_chunks)
Print debugging information for a heapmem zone.
bool heapmem_zone_free(heapmem_zone_t *zone, void *ptr)
Deallocate a chunk of memory in the specified zone.
Header file for the dynamic heap memory allocator.
Header file for the logging system.