rsys

Basic data structures and low-level features
git clone git://git.meso-star.fr/rsys.git
Log | Files | Refs | README | LICENSE

mem_allocator.c (11435B)


      1 /* Copyright (C) 2013-2023, 2025 Vincent Forest (vaplv@free.fr)
      2  *
      3  * The RSys library is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published
      5  * by the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * The RSys library is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with the RSys library. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #define _POSIX_C_SOURCE 200112L /* snprintf support */
     17 #include "math.h"
     18 #include "mem_allocator.h"
     19 #include "mutex.h"
     20 
     21 #include <errno.h>
     22 #if defined(OS_MACH)
     23   #include <malloc/malloc.h>
     24 #else
     25   #include <malloc.h>
     26 #endif
     27 #include <string.h>
     28 
     29 #ifdef OS_WINDOWS
     30   /* On Windows the _aligned_msize function is not defined. The size is thus
     31    * stored into the memory block header */
     32   #define MEM_HEADER_SIZE (2 * sizeof(size_t))
     33   #include "io_c99.h"
     34 #endif
     35 
     36 struct alloc_counter {
     37   ATOMIC nb_allocs;
     38   ATOMIC allocated_size;
     39 };
     40 
     41 /*******************************************************************************
     42  * Common allocation functions
     43  ******************************************************************************/
     44 static struct alloc_counter g_alloc_counter = { 0, 0 };
     45 
     46 void*
     47 mem_alloc(const size_t size)
     48 {
     49   void* mem = NULL;
     50   if(size) {
     51 #if defined(OS_UNIX) || defined (OS_MACH)
     52     mem = malloc(size);
     53     if(mem) {
     54       ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem));
     55       ATOMIC_INCR(&g_alloc_counter.nb_allocs);
     56     }
     57 #elif defined(OS_WINDOWS)
     58     const size_t DEFAULT_ALIGNMENT = 16;
     59     mem = _aligned_offset_malloc
     60       (size + MEM_HEADER_SIZE, DEFAULT_ALIGNMENT, MEM_HEADER_SIZE);
     61     if(mem) {
     62       ((size_t*)mem)[0] = DEFAULT_ALIGNMENT;
     63       ((size_t*)mem)[1] = size + MEM_HEADER_SIZE;
     64       mem = ((char*)mem) + MEM_HEADER_SIZE;
     65       ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem));
     66       ATOMIC_INCR(&g_alloc_counter.nb_allocs);
     67     }
     68 #else
     69   #error "Unsupported OS"
     70 #endif
     71   }
     72   return mem;
     73 }
     74 
     75 void*
     76 mem_calloc(const size_t nelmts, const size_t size)
     77 {
     78   void* mem = NULL;
     79   const size_t alloc_size = nelmts * size;
     80   mem = mem_alloc(alloc_size);
     81   if(mem) {
     82     memset(mem, 0, alloc_size);
     83   }
     84   return mem;
     85 }
     86 
     87 void*
     88 mem_realloc(void* mem, const size_t size)
     89 {
     90   void* new_mem = NULL;
     91 
     92   if(mem == NULL) {
     93     new_mem = mem_alloc(size);
     94   }  else if(size == 0) {
     95     mem_rm(mem);
     96   } else {
     97     const size_t old_size = mem_size(mem);
     98 
     99     ASSERT
    100       (  old_size < SIZE_MAX
    101       && g_alloc_counter.allocated_size >= (int64_t)old_size);
    102     ATOMIC_SUB( &g_alloc_counter.allocated_size, old_size);
    103 
    104 #if defined(OS_WINDOWS)
    105     mem = ((char*)mem) - MEM_HEADER_SIZE;
    106     new_mem = _aligned_offset_realloc
    107       (mem, size + MEM_HEADER_SIZE, ((size_t*)mem)[0], MEM_HEADER_SIZE);
    108     if(new_mem) {
    109       ((size_t*)new_mem)[1] = size + MEM_HEADER_SIZE;
    110       new_mem = ((char*)new_mem) + MEM_HEADER_SIZE;
    111     }
    112 #elif defined(OS_UNIX) || defined(OS_MACH)
    113     new_mem = realloc( mem, size );
    114 #else
    115   #error "Unsupported OS"
    116 #endif
    117     ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(new_mem));
    118   }
    119   return new_mem;
    120 
    121 }
    122 void*
    123 mem_alloc_aligned(const size_t size, const size_t alignment)
    124 {
    125   void* mem = NULL;
    126 
    127   if(size
    128   && IS_POW2( alignment )
    129   && alignment <= 32768 /* 32 KB */) {
    130 #if defined(OS_WINDOWS)
    131     mem = _aligned_offset_malloc
    132       (size + MEM_HEADER_SIZE, alignment, MEM_HEADER_SIZE);
    133     if(mem) {
    134       ((size_t*)mem)[0] = alignment;
    135       ((size_t*)mem)[1] = size + MEM_HEADER_SIZE;
    136       mem = ((char*)mem) + MEM_HEADER_SIZE;
    137       ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem));
    138       ATOMIC_INCR(&g_alloc_counter.nb_allocs);
    139     }
    140 #elif defined(OS_UNIX) || defined(OS_MACH)
    141     const int result = posix_memalign
    142       (&mem, (alignment < sizeof(void*)) ? sizeof(void*) : alignment, size);
    143     (void)result; /* avoid warning in Release */
    144     /* The following assert may not occur due to previous conditions */
    145     ASSERT(result != EINVAL);
    146     ASSERT((result != ENOMEM) || (mem == NULL));
    147     if(mem) {
    148       ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem));
    149       ATOMIC_INCR(&g_alloc_counter.nb_allocs);
    150     }
    151 #else
    152   #error "Unsupported OS"
    153 #endif
    154   }
    155   return mem;
    156 }
    157 
    158 void
    159 mem_rm(void* mem)
    160 {
    161   if(mem) {
    162     ASSERT
    163       (  g_alloc_counter.nb_allocs != 0
    164       && mem_size(mem) < SIZE_MAX
    165       && g_alloc_counter.allocated_size >= (int64_t)mem_size(mem));
    166     ATOMIC_SUB(&g_alloc_counter.allocated_size, mem_size(mem));
    167     ATOMIC_DECR(&g_alloc_counter.nb_allocs);
    168 #if defined(OS_WINDOWS)
    169     mem = ((char*)mem) - MEM_HEADER_SIZE;
    170     _aligned_free( mem );
    171 #elif defined(OS_UNIX) || defined(OS_MACH)
    172     free( mem );
    173 #else
    174   #error "Unsupported OS"
    175 #endif
    176   }
    177 }
    178 
    179 size_t
    180 mem_size(void* mem)
    181 {
    182   size_t mem_size = 0;
    183   if(mem) {
    184 #if defined(OS_WINDOWS)
    185     void* raw_mem = ((char*)mem) - MEM_HEADER_SIZE;
    186     mem_size = ((size_t*)raw_mem)[1];
    187 #elif defined(OS_UNIX)
    188     mem_size = malloc_usable_size(mem);
    189 #elif defined(OS_MACH)
    190     mem_size = malloc_size(mem);
    191 #else
    192   #error "Unsupported OS"
    193 #endif
    194   }
    195   return mem_size;
    196 }
    197 
    198 size_t
    199 mem_allocated_size(void)
    200 {
    201   return (size_t)g_alloc_counter.allocated_size;
    202 }
    203 
    204 /*******************************************************************************
    205  * Default allocator functions
    206  ******************************************************************************/
    207 #define TRACK_DEFAULT_ALLOC /* Enable the tracking of default allocations */
    208 
    209 static void*
    210 default_alloc
    211   (void* data,
    212    const size_t size,
    213    const char* filename,
    214    const unsigned int fileline)
    215 {
    216   void* mem = NULL;
    217 
    218   (void)filename;
    219   (void)fileline;
    220 
    221   if(size) {
    222     mem = mem_alloc(size);
    223 #ifndef TRACK_DEFAULT_ALLOC
    224     (void)data;
    225 #else
    226     ASSERT(data);
    227     if(mem) {
    228       struct alloc_counter* counter = data;
    229       const size_t size_mem = mem_size(mem);
    230       ATOMIC_ADD(&counter->allocated_size, size_mem);
    231       ATOMIC_INCR(&counter->nb_allocs);
    232     }
    233 #endif /* TRACK_DEFAULT_ALLOC */
    234   }
    235   return mem;
    236 }
    237 
    238 static void
    239 default_free(void* data, void* mem)
    240 {
    241   if(mem) {
    242 #ifndef TRACK_DEFAULT_ALLOC
    243     (void)data;
    244 #else
    245     struct alloc_counter* counter = data;
    246     size_t size_mem = mem_size(mem);
    247     ASSERT
    248       ( (data != NULL)
    249       & (counter->nb_allocs != 0)
    250       & (counter->allocated_size >= (int64_t)size_mem));
    251 
    252     ATOMIC_SUB(&counter->allocated_size, size_mem);
    253     ATOMIC_DECR(&counter->nb_allocs);
    254 #endif /* TRACK_DEFAULT_ALLOC */
    255     mem_rm(mem);
    256   }
    257 }
    258 
    259 static void*
    260 default_calloc
    261   (void* data,
    262    const size_t nbelmts,
    263    const size_t size,
    264    const char* filename,
    265    const unsigned int fileline)
    266 {
    267   void* mem = NULL;
    268   const size_t alloc_size = nbelmts * size;
    269 
    270   mem = default_alloc(data, alloc_size, filename, fileline);
    271   if(mem) {
    272     memset(mem, 0, alloc_size);
    273   }
    274   return mem;
    275 }
    276 
    277 static void*
    278 default_realloc
    279   (void* data,
    280    void* mem,
    281    const size_t size,
    282    const char* filename,
    283    const unsigned int fileline)
    284 {
    285   void* new_mem = NULL;
    286 
    287 #ifndef TRACK_DEFAULT_ALLOC
    288   (void)data;
    289   (void)filename;
    290   (void)fileline;
    291   new_mem = mem_realloc(mem, size);
    292 #else
    293   ASSERT(data);
    294   if(!mem) {
    295     new_mem = default_alloc(data, size, filename, fileline);
    296   } else {
    297     if(size == 0) {
    298       default_free(data, mem);
    299     } else {
    300       struct alloc_counter* counter = data;
    301       const size_t size_old = mem_size(mem);
    302       size_t size_new = 0;
    303 
    304       ASSERT(counter->allocated_size >= (int64_t)size_old);
    305       ATOMIC_SUB(&counter->allocated_size, size_old);
    306 
    307       new_mem = mem_realloc(mem, size);
    308       size_new = mem_size(new_mem);
    309       ATOMIC_ADD(&counter->allocated_size, size_new);
    310     }
    311   }
    312 #endif /* TRACK_DEFAULT_ALLOC */
    313   return new_mem;
    314 }
    315 
    316 static void*
    317 default_alloc_aligned
    318   (void* data,
    319    const size_t size,
    320    const size_t alignment,
    321    const char* filename,
    322    const unsigned int fileline)
    323 {
    324   void* mem = NULL;
    325 
    326   (void)filename;
    327   (void)fileline;
    328 
    329   if(size && IS_POW2(alignment)) {
    330     mem = mem_alloc_aligned(size, alignment);
    331 #ifndef TRACK_DEFAULT_ALLOC
    332     (void)data;
    333     #else
    334     ASSERT(data);
    335     if(mem) {
    336       struct alloc_counter* counter = data;
    337       const size_t size_mem = mem_size(mem);
    338       ATOMIC_ADD(&counter->allocated_size, size_mem);
    339       ATOMIC_INCR(&counter->nb_allocs);
    340     }
    341 #endif /* TRACK_DEFAULT_ALLOC */
    342   }
    343   return mem;
    344 }
    345 
    346 static size_t
    347 default_mem_size(void* data, void* mem)
    348 {
    349   (void)data;
    350   return mem_size(mem);
    351 }
    352 
    353 static size_t
    354 default_allocated_size(const void* data)
    355 {
    356 #ifndef TRACK_DEFAULT_ALLOC
    357   (void)data;
    358   return 0;
    359 #else
    360   const struct alloc_counter* counter = data;
    361   ASSERT(counter != NULL);
    362   return (size_t)counter->allocated_size;
    363 #endif /* TRACK_DEFAULT_ALLOC */
    364 }
    365 
    366 static size_t
    367 default_dump
    368   (const void* data,
    369    char* dump,
    370    const size_t max_dump_len)
    371 {
    372 #ifndef TRACK_DEFAULT_ALLOC
    373   (void)data;
    374   if(dump && max_dump_len)
    375     dump[0] = '\0';
    376   return 0;
    377 
    378 #else
    379   const struct alloc_counter* counter = data;
    380   size_t dump_len = 0;
    381   int len = 0;
    382 
    383   ASSERT(counter && (!max_dump_len || dump));
    384 
    385   len = snprintf
    386     (dump,
    387      max_dump_len,
    388      "%lu bytes allocated in %lu allocations.",
    389      (unsigned long)counter->allocated_size,
    390      (unsigned long)counter->nb_allocs);
    391   ASSERT(len >= 0);
    392   dump_len = (size_t)len;
    393 
    394   if((size_t)len >= (max_dump_len - 1)) /* -1 <=> null char. */
    395     dump[max_dump_len-1] = '\0';
    396 
    397   return dump_len;
    398 #endif
    399 }
    400 
    401 /*******************************************************************************
    402  * Default allocator
    403  ******************************************************************************/
    404 static struct alloc_counter default_alloc_counter = {0, 0};
    405 
    406 struct mem_allocator mem_default_allocator = {
    407   default_alloc,
    408   default_calloc,
    409   default_realloc,
    410   default_alloc_aligned,
    411   default_free,
    412   default_mem_size,
    413   default_allocated_size,
    414   default_dump,
    415   (void*)&default_alloc_counter
    416 };
    417 
    418 /*******************************************************************************
    419  * Regular allocator
    420  ******************************************************************************/
    421 res_T
    422 mem_init_regular_allocator(struct mem_allocator* allocator)
    423 {
    424   struct alloc_counter* counter = NULL;
    425   res_T res = RES_OK;
    426 
    427   if(!allocator) {
    428     res = RES_BAD_ARG;
    429     goto error;
    430   }
    431   memset(allocator, 0, sizeof(struct mem_allocator));
    432 
    433   counter = mem_calloc(1, sizeof(struct alloc_counter));
    434   if(!counter) {
    435     res = RES_MEM_ERR;
    436     goto error;
    437   }
    438 
    439   allocator->alloc = default_alloc;
    440   allocator->calloc = default_calloc;
    441   allocator->realloc = default_realloc;
    442   allocator->mem_size = default_mem_size;
    443   allocator->alloc_aligned = default_alloc_aligned;
    444   allocator->rm = default_free;
    445   allocator->allocated_size = default_allocated_size;
    446   allocator->dump = default_dump;
    447   allocator->data = (void*)counter;
    448 
    449 exit:
    450   return res;
    451 error:
    452   if(allocator) mem_shutdown_regular_allocator(allocator);
    453   goto exit;
    454 }
    455 
    456 void
    457 mem_shutdown_regular_allocator(struct mem_allocator* allocator)
    458 {
    459   struct alloc_counter* counter;
    460   ASSERT(allocator);
    461 
    462   counter = allocator->data;
    463   if(counter) {
    464     ASSERT(!counter->allocated_size && !counter->nb_allocs);
    465     mem_rm(counter);
    466   }
    467   memset(allocator, 0, sizeof(struct mem_allocator));
    468 }