1
// =============================================================== //
3
// File : admalloc.cxx //
6
// Institute of Microbiology (Technical University Munich) //
7
// http://www.arb-home.de/ //
9
// =============================================================== //
15
#include <arb_backtrace.h>
16
#include "gb_storage.h"
18
// #define DUMP_MEMBLKS
19
// #define DUMP_MEMBLKS_AT_EXIT
22
// #define TEST_MEMBLKS
23
// #define TRACE_ALLOCS
24
#if defined(WARN_TODO)
25
#warning unit tests fail when TRACE_ALLOCS is defined (due to wrong sized block during load(?). cant fix atm. see [6672])
29
#define GBM_MAGIC 0x74732876
31
#define GBM_SYSTEM_PAGE_SIZE 4096 // 4k Tables
32
#define GBM_MALLOC_OVERHEAD 32 // pointer for alloc
33
#define GBM_TABLE_SIZE (GBM_SYSTEM_PAGE_SIZE-GBM_MALLOC_OVERHEAD) // usable size of table
36
#define GBM_LD_ALIGNED 3
38
#define GBM_MAX_TABLES 16 // n different sizes -> max = GBM_MAX_TABLES * GBM_ALIGNED
39
#define GBM_MAX_SIZE (GBM_MAX_TABLES*GBM_ALIGNED)
40
#define GBM_MAX_INDEX 256 // has to be 2 ^ x (with x = GBM_MAX_TABLES ? )
44
long magic; // indicates free element
45
gbm_data *next; // next free element
48
struct gbm_table { // a block containing data
53
static struct gbm_pool { // one pool for each memory index
54
gbm_data *gds; // free data area
55
size_t size; // free size of current table
56
size_t allsize; // full size of all tables
57
gbm_table *first; // link list of tables
58
gbm_data *tables[GBM_MAX_TABLES+1]; // free entries
59
long tablecnt[GBM_MAX_TABLES+1]; // number of free entries
60
long useditems[GBM_MAX_TABLES+1]; // number of used items (everything)
61
size_t extern_data_size; // not handled by this routine
62
long extern_data_items;
63
} gbm_pool4idx[GBM_MAX_INDEX];
70
#define GBB_INCR 11 // memsize increment in percent between adjacent clusters
71
#define GBB_CLUSTERS 64 // # of different clusters
72
#define GBB_ALIGN GBM_LD_ALIGNED // align memsize of clusters (# of bits)
73
#define GBB_MINSIZE GBM_MAX_SIZE // minimal size of allocated big block
74
#define GBB_MAX_TRIALS 4 // maximal number of clusters to search for an unused block
75
#define GBB_MAGIC 0x67823747
79
struct gbb_freedata // part of gbb_data if it`s a free block
81
// cppcheck-suppress unusedStructMember
83
gbb_data *next; // next unused memblock
87
size_t size; // real size of memblock (from `content` to end of block)
88
long allocFromSystem; // ==0 -> it`s a block imported by gbm_put_mem
89
gbb_freedata content; // startposition of block returned to user or chain info for free blocks
92
#define GBB_HEADER_SIZE (sizeof(gbb_data)-sizeof(gbb_freedata))
94
static struct gbb_Cluster
96
size_t size; // minimum size of memblocks in this cluster
97
gbb_data *first; // first free block
99
} gbb_cluster[GBB_CLUSTERS+1];
102
NOT4PERL void *GB_calloc(unsigned int nelem, unsigned int elsize)
104
size_t size = nelem*elsize;
105
void *mem = malloc(size);
108
memset(mem, 0, size);
111
fprintf(stderr, "Panic Error: insufficient memory: tried to get %u*%u bytes\n", nelem, elsize);
116
NOT4PERL void *GB_recalloc(void *ptr, unsigned int oelem, unsigned int nelem, unsigned int elsize)
118
size_t nsize = nelem*elsize;
119
void *mem = malloc(nsize);
122
size_t osize = oelem*elsize;
125
memmove(mem, ptr, osize);
127
memset(((char*)mem)+osize, 0, nsize-osize);
131
memmove(mem, ptr, nsize);
135
fprintf(stderr, "Panic Error: insufficient memory: tried to get %u*%u bytes\n", nelem, elsize);
143
class AllocLogEntry {
148
mutable BackTraceInfo *trace;
151
AllocLogEntry(void *block_, size_t size_, long index_, bool do_trace)
155
, trace(do_trace ? GBK_get_backtrace(5) : NULL)
157
AllocLogEntry(const AllocLogEntry& other)
165
~AllocLogEntry() { if (trace) GBK_free_backtrace(trace); }
167
size_t get_size() const { return size; }
168
long get_index() const { return index; }
170
bool operator<(const AllocLogEntry& other) const { return block < other.block; }
171
void dump(FILE *out, GB_CSTR message) const { GBK_dump_former_backtrace(trace, out, message); }
174
typedef std::set<AllocLogEntry> AllocLogEntries;
177
AllocLogEntries entries;
179
const AllocLogEntry *existingEntry(void *block) {
180
AllocLogEntries::const_iterator found = entries.find(AllocLogEntry(block, 0, 0, false));
182
return found == entries.end() ? NULL : &*found;
189
size_t count = entries.size();
191
fprintf(stderr, "%zu non-freed blocks:\n", count);
192
AllocLogEntries::const_iterator end = entries.end();
193
for (AllocLogEntries::const_iterator entry = entries.begin(); entry != end; ++entry) {
194
entry->dump(stderr, "block was allocated from here");
199
void allocated(void *block, size_t size, long index) {
200
const AllocLogEntry *exists = existingEntry(block);
202
GBK_dump_backtrace(stderr, "Block allocated again");
203
exists->dump(stderr, "Already allocated from here");
206
entries.insert(AllocLogEntry(block, size, index, true));
209
void freed(void *block, size_t size, long index) {
210
const AllocLogEntry *exists = existingEntry(block);
212
if (!gb_isMappedMemory(block)) {
214
// GBK_dump_backtrace(stderr, "Tried to free unallocated block");
218
gb_assert(exists->get_size() == size);
219
gb_assert(exists->get_index() == index);
220
entries.erase(*exists);
225
static AllocLogger allocLogger;
229
inline void free_gbm_table(gbm_table *table) {
231
gbm_table *next = table->next;
238
static bool gbm_mem_initialized = false;
240
void gbm_flush_mem() {
241
gb_assert(gbm_mem_initialized);
243
for (int i = 0; i<GBM_MAX_INDEX; ++i) {
244
gbm_pool& gbm = gbm_pool4idx[i];
245
bool have_used_items = false;
247
for (int t = 0; t < GBM_MAX_TABLES; t++) {
248
if (gbm.useditems[t]) {
249
have_used_items = true;
254
if (!have_used_items) {
255
free_gbm_table(gbm.first);
256
memset((char*)&gbm, 0, sizeof(gbm));
261
void gbm_init_mem() {
262
if (!gbm_mem_initialized) {
263
for (int i = 0; i<GBM_MAX_INDEX; ++i) {
264
memset((char *)&gbm_pool4idx[i], 0, sizeof(gbm_pool));
265
gbm_pool4idx[i].tables[0] = 0; // CORE zero get mem
267
gbm_global.old_sbrk = (char *)sbrk(0);
272
gbb_cluster[0].size = GBB_MINSIZE;
273
gbb_cluster[0].first = NULL;
275
for (int i = 1; i<GBB_CLUSTERS; ++i) {
276
long nextSize = gbb_cluster[i-1].size * (100+GBB_INCR);
279
nextSize >>= GBB_ALIGN;
281
nextSize <<= GBB_ALIGN;
283
gbb_cluster[i].size = nextSize;
284
gbb_cluster[i].first = NULL;
287
// last cluster contains ALL bigger blocks
289
gbb_cluster[GBB_CLUSTERS].size = INT_MAX;
290
gbb_cluster[GBB_CLUSTERS].first = NULL;
292
gbm_mem_initialized = true;
296
struct ARBDB_memory_manager {
297
ARBDB_memory_manager() {
298
gb_assert(!gbm_mem_initialized); // there may be only one instance!
301
~ARBDB_memory_manager() {
302
#if defined(DUMP_MEMBLKS_AT_EXIT)
303
printf("memory at exit:\n");
305
#endif // DUMP_MEMBLKS_AT_EXIT
307
#if defined(DUMP_MEMBLKS_AT_EXIT)
308
printf("memory at exit (after flush):\n");
310
#endif // DUMP_MEMBLKS_AT_EXIT
313
static ARBDB_memory_manager memman;
317
GB_internal_error("memory allocation error - maybe you're out of swap space?");
322
#define TEST() testMemblocks(__FILE__, __LINE__)
324
void testMemblocks(const char *file, int line)
328
for (idx=0; idx<GBB_CLUSTERS; idx++)
330
struct gbb_Cluster *cl = &(gbb_cluster[idx]);
331
gbb_data *blk = cl->first;
335
if (blk->size<cl->size)
337
fprintf(stderr, "Illegal block (size=%zu) in cluster %i (size=%zu) (%s,%i)\n", blk->size, idx, cl->size, file, line);
340
blk = blk->content.next;
352
static void imemerr(const char *why)
354
GB_internal_errorf("Dangerous internal error: '%s'\n"
355
"Inconsistent database: Do not overwrite old files with this database", why);
358
static int getClusterIndex(size_t size) /* searches the index of the
359
lowest cluster for that:
360
size <= cluster->size */
364
if (size<GBB_MINSIZE) return 0;
372
if (gbb_cluster[m].size < size) l = m+1;
376
gb_assert(l<=GBB_CLUSTERS);
381
static void gbm_put_memblk(char *memblk, size_t size) {
382
/* gives any memory block (allocated or not)
383
into the responsibility of this module;
384
the block has to be aligned!!! */
392
printf("put %p (%li bytes)\n", memblk, size);
395
if (size<(GBB_HEADER_SIZE+GBB_MINSIZE))
397
GB_internal_errorf("gmb_put_memblk() called with size below %zu bytes",
398
GBB_HEADER_SIZE+GBB_MINSIZE);
402
block = (gbb_data *)memblk;
403
block->size = size-GBB_HEADER_SIZE;
404
block->allocFromSystem = 0;
406
idx = getClusterIndex(block->size)-1;
407
if (idx<0) { // (silences warning in NDEBUG mode)
408
gb_assert(0); // should be impossible
412
block->content.next = gbb_cluster[idx].first;
413
block->content.magic = GBB_MAGIC;
414
gbb_cluster[idx].first = block;
416
gb_assert(idx==GBB_CLUSTERS || block->size>=gbb_cluster[idx].size);
420
static char *gbm_get_memblk(size_t size) {
421
gbb_data *block = NULL;
422
int trials = GBB_MAX_TRIALS;
427
idx = getClusterIndex(size);
428
gb_assert(gbb_cluster[idx].size>=size);
430
while (trials--) // search a cluster containing a block
432
if ((block = gbb_cluster[idx].first)!=NULL) break; // found!
433
if (idx==GBB_CLUSTERS) break; // last cluster!
437
if (!block) // if no unused block -> allocate from system
443
allocationSize = (idx==GBB_CLUSTERS
445
: (size_t)(gbb_cluster[idx].size)) + GBB_HEADER_SIZE;
447
block = (gbb_data *)GB_calloc(1, allocationSize);
448
if (!block) { GB_memerr(); return NULL; }
450
block->size = allocationSize-GBB_HEADER_SIZE;
451
block->allocFromSystem = 1;
453
gb_assert(block->size>=size);
456
printf("allocated %li bytes\n", size);
461
gbb_data **blockPtr = &(gbb_cluster[idx].first);
463
if (idx==GBB_CLUSTERS) // last cluster (test for block size necessary)
465
while ((block=*blockPtr)!=NULL && block->size<size)
466
blockPtr = &(block->content.next);
468
if (!block) goto allocFromSys;
469
gb_assert(block->size>=size);
472
if (block->content.magic!=GBB_MAGIC) { imemerr("bad magic number if free block"); return NULL; }
473
*blockPtr = block->content.next;
474
memset((char*)&(block->content), 0, size); // act like calloc()
477
printf("using unused block "
478
"(add=%p,size=%li, block->size=%li,cluster->size=%li)\n",
479
block, size, block->size, gbb_cluster[idx].size);
482
gb_assert(block->size>=size);
485
gb_assert(block->size>=size);
489
return (char*)&(block->content);
492
inline void *GB_MEMALIGN(size_t alignment, size_t size) {
494
int err = posix_memalign(&mem, alignment, size);
495
if (err) GBK_terminatef("ARBDB allocation error (errcode=%i)", err);
499
void *gbmGetMemImpl(size_t size, long index) {
500
if (size < sizeof(gbm_data)) size = sizeof(gbm_data);
501
index &= GBM_MAX_INDEX-1;
503
gbm_pool *ggi = &gbm_pool4idx[index];
504
unsigned long nsize = (size + (GBM_ALIGNED - 1)) & (-GBM_ALIGNED);
507
if (nsize > GBM_MAX_SIZE) {
508
ggi->extern_data_size += nsize;
509
ggi->extern_data_items++;
511
result = gbm_get_memblk((size_t)nsize);
514
unsigned long pos = nsize >> GBM_LD_ALIGNED;
515
gbm_data *gds = ggi->tables[pos];
517
ggi->tablecnt[pos]--;
518
result = (char *)gds;
519
if (gds->magic != GBM_MAGIC) {
520
printf("%lX!= %lX\n", gds->magic, (long)GBM_MAGIC);
521
GB_internal_error("Dangerous internal error: Inconsistent database: "
522
"Do not overwrite old files with this database");
524
ggi->tables[pos] = ggi->tables[pos]->next;
527
if (ggi->size < nsize) {
528
gbm_table *gts = (gbm_table *)GB_MEMALIGN(GBM_SYSTEM_PAGE_SIZE, GBM_TABLE_SIZE);
530
if (!gts) { GB_memerr(); return NULL; }
532
memset((char *)gts, 0, GBM_TABLE_SIZE);
533
ggi->gds = >s->data[0];
534
gts->next = ggi->first; // link tables
536
ggi->size = GBM_TABLE_SIZE - sizeof(void *);
537
ggi->allsize += GBM_TABLE_SIZE;
539
result = (char *)ggi->gds;
540
ggi->gds = (gbm_data *)(((char *)ggi->gds) + nsize);
541
ggi->size -= (size_t)nsize;
544
ggi->useditems[pos]++;
545
memset(result, 0, nsize); // act like calloc()
549
allocLogger.allocated(erg, size, index);
554
void gbmFreeMemImpl(void *data, size_t size, long index) {
555
if (size < sizeof(gbm_data)) size = sizeof(gbm_data);
556
index &= GBM_MAX_INDEX-1;
558
gbm_pool *ggi = &gbm_pool4idx[index];
559
long nsize = (size + (GBM_ALIGNED - 1)) & (-GBM_ALIGNED);
562
allocLogger.freed(data, size, index);
565
if (nsize > GBM_MAX_SIZE) {
568
if (gb_isMappedMemory(data)) {
569
block = (gbb_data *)data;
571
block->size = size-GBB_HEADER_SIZE;
572
block->allocFromSystem = 0;
574
if (size>=(GBB_HEADER_SIZE+GBB_MINSIZE)) {
575
gbm_put_memblk((char*)block, size);
579
block = (gbb_data *)((char*)data-GBB_HEADER_SIZE);
581
ggi->extern_data_size -= (size_t)nsize;
582
ggi->extern_data_items--;
584
if (block->size<size) { imemerr("block size does not match"); return; }
586
if (block->allocFromSystem) {
590
/* printf("put unused block (size=%li block->size=%li)\n",
591
size,block->size); */
592
gbm_put_memblk((char*)block, block->size + GBB_HEADER_SIZE);
598
if (gb_isMappedMemory(data)) return; // @@@ reason: size may be shorter
600
gbm_data *gdata = (gbm_data*)data;
602
if (gdata->magic == GBM_MAGIC) {
603
imemerr("double free");
607
long pos = nsize >> GBM_LD_ALIGNED;
609
gdata->next = ggi->tables[pos];
610
gdata->magic = GBM_MAGIC;
611
ggi->tables[pos] = gdata;
612
ggi->tablecnt[pos]++;
613
ggi->useditems[pos]--;
617
#endif // MEMORY_TEST==0
619
void gbm_debug_mem() {
626
printf("Memory Debug Information:\n");
627
for (index = 0; index < GBM_MAX_INDEX; index++)
630
ggi = &gbm_pool4idx[index];
631
for (i = 0; i < GBM_MAX_TABLES; i++)
633
index_total += i * GBM_ALIGNED * (int) ggi->useditems[i];
634
total += i * GBM_ALIGNED * (int) ggi->useditems[i];
636
if (ggi->useditems[i] || ggi->tablecnt[i]) {
637
printf("\t'I=%3i' 'Size=%3i' * 'Items %4i' = 'size %7i' 'sum=%7li' 'totalsum=%7li' : Free %3i\n",
640
(int) ggi->useditems[i],
641
i * GBM_ALIGNED * (int) ggi->useditems[i],
644
(int) ggi->tablecnt[i]);
647
if (ggi->extern_data_size) {
648
index_total += ggi->extern_data_size;
649
total += ggi->extern_data_size;
650
printf("\t'I=%3i' External Data Items=%3li = Sum=%3li 'sum=%7li' 'total=%7li\n",
652
ggi->extern_data_items,
653
(long)ggi->extern_data_size,
660
char *topofmem = (char *)sbrk(0);
661
printf("spbrk %lx old %lx size %ti\n",
663
(long)gbm_global.old_sbrk,
664
topofmem-gbm_global.old_sbrk);
668
// --------------------------------------------------------------------------------
670
#if defined(UNIT_TESTS) && 0
672
#include <test_unit.h>
674
void TEST_ARBDB_memory() { // not a real unit test - just was used for debugging
676
long *blocks[ALLOCS];
678
#if (MEMORY_TEST == 0)
679
printf("Before allocations:\n");
683
static int16_t non_alloc[75]; // 150 byte
684
gbm_put_memblk((char*)non_alloc, sizeof(non_alloc));
686
printf("Added one non-allocated block:\n");
691
for (int pass = 1; pass <= 2; ++pass) {
694
for (size_t size = 10; size<5000; size += size/3) {
695
for (long index = 0; index<3; ++index) {
697
long *block = (long*)gbm_get_mem(size, index);
702
blocks[allocs++] = block;
705
long *block = blocks[allocs++];
706
gbm_free_mem(block, (size_t)block[0], block[1]);
711
#if (MEMORY_TEST == 0)
713
printf("%li memory blocks allocated:\n", allocs);
717
printf("Memory freed:\n");
720
printf("Memory flushed:\n");
725
gb_assert(allocs == ALLOCS);
728
GBK_dump_backtrace(stderr, "test");