Call InitializeCriticalSection() on the sdt_mutex in
[platform/upstream/glib.git] / glib / gslice.c
1 /* GLIB sliced memory - fast concurrent memory chunk allocator
2  * Copyright (C) 2005 Tim Janik
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 /* MT safe */
20
21 #include "config.h"
22
23 #if     defined HAVE_POSIX_MEMALIGN && defined POSIX_MEMALIGN_WITH_COMPLIANT_ALLOCS
24 #  define HAVE_COMPLIANT_POSIX_MEMALIGN 1
25 #endif
26
27 #ifdef HAVE_COMPLIANT_POSIX_MEMALIGN
28 #define _XOPEN_SOURCE 600       /* posix_memalign() */
29 #endif
30 #include <stdlib.h>             /* posix_memalign() */
31 #include <string.h>
32 #include <errno.h>
33 #include "gmem.h"               /* gslice.h */
34 #include "gthreadprivate.h"
35 #include "glib.h"
36 #include "galias.h"
37 #ifdef HAVE_UNISTD_H
38 #include <unistd.h>             /* sysconf() */
39 #endif
40 #ifdef G_OS_WIN32
41 #include <windows.h>
42 #include <process.h>
43 #endif
44
45 /* the GSlice allocator is split up into 4 layers, roughly modelled after the slab
46  * allocator and magazine extensions as outlined in:
47  * + [Bonwick94] Jeff Bonwick, The slab allocator: An object-caching kernel
48  *   memory allocator. USENIX 1994, http://citeseer.ist.psu.edu/bonwick94slab.html
49  * + [Bonwick01] Bonwick and Jonathan Adams, Magazines and vmem: Extending the
50  *   slab allocator to many cpu's and arbitrary resources.
51  *   USENIX 2001, http://citeseer.ist.psu.edu/bonwick01magazines.html
52  * the layers are:
53  * - the thread magazines. for each (aligned) chunk size, a magazine (a list)
54  *   of recently freed and soon to be allocated chunks is maintained per thread.
55  *   this way, most alloc/free requests can be quickly satisfied from per-thread
56  *   free lists which only require one g_private_get() call to retrive the
57  *   thread handle.
58  * - the magazine cache. allocating and freeing chunks to/from threads only
59  *   occours at magazine sizes from a global depot of magazines. the depot
60  *   maintaines a 15 second working set of allocated magazines, so full
61  *   magazines are not allocated and released too often.
62  *   the chunk size dependent magazine sizes automatically adapt (within limits,
63  *   see [3]) to lock contention to properly scale performance across a variety
64  *   of SMP systems.
65  * - the slab allocator. this allocator allocates slabs (blocks of memory) close
66  *   to the system page size or multiples thereof which have to be page aligned.
67  *   the blocks are divided into smaller chunks which are used to satisfy
68  *   allocations from the upper layers. the space provided by the reminder of
69  *   the chunk size division is used for cache colorization (random distribution
70  *   of chunk addresses) to improve processor cache utilization. multiple slabs
71  *   with the same chunk size are kept in a partially sorted ring to allow O(1)
72  *   freeing and allocation of chunks (as long as the allocation of an entirely
73  *   new slab can be avoided).
74  * - the page allocator. on most modern systems, posix_memalign(3) or
75  *   memalign(3) should be available, so this is used to allocate blocks with
76  *   system page size based alignments and sizes or multiples thereof.
77  *   if no memalign variant is provided, valloc() is used instead and
78  *   block sizes are limited to the system page size (no multiples thereof).
79  *   as a fallback, on system without even valloc(), a malloc(3)-based page
80  *   allocator with alloc-only behaviour is used.
81  *
82  * NOTES:
83  * [1] some systems memalign(3) implementations may rely on boundary tagging for
84  *     the handed out memory chunks. to avoid excessive page-wise fragmentation,
85  *     we reserve 2 * sizeof (void*) per block size for the systems memalign(3),
86  *     specified in NATIVE_MALLOC_PADDING.
87  * [2] using the slab allocator alone already provides for a fast and efficient
88  *     allocator, it doesn't properly scale beyond single-threaded uses though.
89  *     also, the slab allocator implements eager free(3)-ing, i.e. does not
90  *     provide any form of caching or working set maintenance. so if used alone,
91  *     it's vulnerable to trashing for sequences of balanced (alloc, free) pairs
92  *     at certain thresholds.
93  * [3] magazine sizes are bound by an implementation specific minimum size and
94  *     a chunk size specific maximum to limit magazine storage sizes to roughly
95  *     16KB.
96  * [4] allocating ca. 8 chunks per block/page keeps a good balance between
97  *     external and internal fragmentation (<= 12.5%). [Bonwick94]
98  */
99
100 /* --- macros and constants --- */
101 #define LARGEALIGNMENT          (256)
102 #define P2ALIGNMENT             (2 * sizeof (gsize))                            /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */
103 #define ALIGN(size, base)       ((base) * (gsize) (((size) + (base) - 1) / (base)))
104 #define NATIVE_MALLOC_PADDING   P2ALIGNMENT                                     /* per-page padding left for native malloc(3) see [1] */
105 #define SLAB_INFO_SIZE          P2ALIGN (sizeof (SlabInfo) + NATIVE_MALLOC_PADDING)
106 #define MAX_MAGAZINE_SIZE       (256)                                           /* see [3] and allocator_get_magazine_threshold() for this */
107 #define MIN_MAGAZINE_SIZE       (4)
108 #define MAX_STAMP_COUNTER       (7)                                             /* distributes the load of gettimeofday() */
109 #define MAX_SLAB_CHUNK_SIZE(al) (((al)->max_page_size - SLAB_INFO_SIZE) / 8)    /* we want at last 8 chunks per page, see [4] */
110 #define MAX_SLAB_INDEX(al)      (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1)
111 #define SLAB_INDEX(al, asize)   ((asize) / P2ALIGNMENT - 1)                     /* asize must be P2ALIGNMENT aligned */
112 #define SLAB_CHUNK_SIZE(al, ix) (((ix) + 1) * P2ALIGNMENT)
113 #define SLAB_BPAGE_SIZE(al,csz) (8 * (csz) + SLAB_INFO_SIZE)
114
115 /* optimized version of ALIGN (size, P2ALIGNMENT) */
116 #if     GLIB_SIZEOF_SIZE_T * 2 == 8  /* P2ALIGNMENT */
117 #define P2ALIGN(size)   (((size) + 0x7) & ~(gsize) 0x7)
118 #elif   GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */
119 #define P2ALIGN(size)   (((size) + 0xf) & ~(gsize) 0xf)
120 #else
121 #define P2ALIGN(size)   ALIGN (size, P2ALIGNMENT)
122 #endif
123
124 /* special helpers to avoid gmessage.c dependency */
125 static void mem_error (const char *format, ...) G_GNUC_PRINTF (1,2);
126 #define mem_assert(cond)    do { if (G_LIKELY (cond)) ; else mem_error ("assertion failed: %s", #cond); } while (0)
127
128 /* --- structures --- */
129 typedef struct _ChunkLink      ChunkLink;
130 typedef struct _SlabInfo       SlabInfo;
131 typedef struct _CachedMagazine CachedMagazine;
132 struct _ChunkLink {
133   ChunkLink *next;
134   ChunkLink *data;
135 };
136 struct _SlabInfo {
137   ChunkLink *chunks;
138   guint n_allocated;
139   SlabInfo *next, *prev;
140 };
141 typedef struct {
142   ChunkLink *chunks;
143   gsize      count;                     /* approximative chunks list length */
144 } Magazine;
145 typedef struct {
146   Magazine   *magazine1;                /* array of MAX_SLAB_INDEX (allocator) */
147   Magazine   *magazine2;                /* array of MAX_SLAB_INDEX (allocator) */
148 } ThreadMemory;
149 typedef struct {
150   gboolean always_malloc;
151   gboolean bypass_magazines;
152   gboolean debug_blocks;
153   gsize    working_set_msecs;
154   guint    color_increment;
155 } SliceConfig;
156 typedef struct {
157   /* const after initialization */
158   gsize         min_page_size, max_page_size;
159   SliceConfig   config;
160   gsize         max_slab_chunk_size_for_magazine_cache;
161   /* magazine cache */
162   GMutex       *magazine_mutex;
163   ChunkLink   **magazines;                /* array of MAX_SLAB_INDEX (allocator) */
164   guint        *contention_counters;      /* array of MAX_SLAB_INDEX (allocator) */
165   gint          mutex_counter;
166   guint         stamp_counter;
167   guint         last_stamp;
168   /* slab allocator */
169   GMutex       *slab_mutex;
170   SlabInfo    **slab_stack;                /* array of MAX_SLAB_INDEX (allocator) */
171   guint        color_accu;
172 } Allocator;
173
174 /* --- g-slice prototypes --- */
175 static gpointer     slab_allocator_alloc_chunk       (gsize      chunk_size);
176 static void         slab_allocator_free_chunk        (gsize      chunk_size,
177                                                       gpointer   mem);
178 static void         private_thread_memory_cleanup    (gpointer   data);
179 static gpointer     allocator_memalign               (gsize      alignment,
180                                                       gsize      memsize);
181 static void         allocator_memfree                (gsize      memsize,
182                                                       gpointer   mem);
183 static inline void  magazine_cache_update_stamp      (void);
184 static inline gsize allocator_get_magazine_threshold (Allocator *allocator,
185                                                       guint      ix);
186
187 /* --- g-slice memory checker --- */
188 static void     smc_notify_alloc  (void   *pointer,
189                                    size_t  size);
190 static int      smc_notify_free   (void   *pointer,
191                                    size_t  size);
192
193 /* --- variables --- */
194 static GPrivate   *private_thread_memory = NULL;
195 static gsize       sys_page_size = 0;
196 static Allocator   allocator[1] = { { 0, }, };
197 static SliceConfig slice_config = {
198   FALSE,        /* always_malloc */
199   FALSE,        /* bypass_magazines */
200   FALSE,        /* debug_blocks */
201   15 * 1000,    /* working_set_msecs */
202   1,            /* color increment, alt: 0x7fffffff */
203 };
204 static GMutex     *smc_tree_mutex = NULL; /* mutex for G_SLICE=debug-blocks */
205
206 #ifndef G_OS_WIN32
207
208 #include <pthread.h>
209 static pthread_mutex_t sdt_mutex = PTHREAD_MUTEX_INITIALIZER;
210 #define SDT_LOCK() pthread_mutex_lock (&sdt_mutex)
211 #define SDT_UNLOCK() pthread_mutex_unlock (&sdt_mutex)
212
213 #else
214
215 /* We don't want to depend on pthreads on Win32, at least for now. But
216  * if we change GThread to use pthreads-win32 we could use pthreads
217  * here, too.
218  */
219
220 static CRITICAL_SECTION sdt_mutex;
221 #define SDT_LOCK() EnterCriticalSection (&sdt_mutex)
222 #define SDT_UNLOCK() LeaveCriticalSection (&sdt_mutex)
223
224 #endif
225
226 /* --- auxillary funcitons --- */
227 void
228 g_slice_set_config (GSliceConfig ckey,
229                     gint64       value)
230 {
231   g_return_if_fail (sys_page_size == 0);
232   switch (ckey)
233     {
234     case G_SLICE_CONFIG_ALWAYS_MALLOC:
235       slice_config.always_malloc = value != 0;
236       break;
237     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
238       slice_config.bypass_magazines = value != 0;
239       break;
240     case G_SLICE_CONFIG_WORKING_SET_MSECS:
241       slice_config.working_set_msecs = value;
242       break;
243     case G_SLICE_CONFIG_COLOR_INCREMENT:
244       slice_config.color_increment = value;
245     default: ;
246     }
247 }
248
249 gint64
250 g_slice_get_config (GSliceConfig ckey)
251 {
252   switch (ckey)
253     {
254     case G_SLICE_CONFIG_ALWAYS_MALLOC:
255       return slice_config.always_malloc;
256     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
257       return slice_config.bypass_magazines;
258     case G_SLICE_CONFIG_WORKING_SET_MSECS:
259       return slice_config.working_set_msecs;
260     case G_SLICE_CONFIG_CHUNK_SIZES:
261       return MAX_SLAB_INDEX (allocator);
262     case G_SLICE_CONFIG_COLOR_INCREMENT:
263       return slice_config.color_increment;
264     default:
265       return 0;
266     }
267 }
268
269 gint64*
270 g_slice_get_config_state (GSliceConfig ckey,
271                           gint64       address,
272                           guint       *n_values)
273 {
274   guint i = 0;
275   g_return_val_if_fail (n_values != NULL, NULL);
276   *n_values = 0;
277   switch (ckey)
278     {
279       gint64 array[64];
280     case G_SLICE_CONFIG_CONTENTION_COUNTER:
281       array[i++] = SLAB_CHUNK_SIZE (allocator, address);
282       array[i++] = allocator->contention_counters[address];
283       array[i++] = allocator_get_magazine_threshold (allocator, address);
284       *n_values = i;
285       return g_memdup (array, sizeof (array[0]) * *n_values);
286     default:
287       return NULL;
288     }
289 }
290
291 static void
292 slice_config_init (SliceConfig *config)
293 {
294   /* don't use g_malloc/g_message here */
295   gchar buffer[1024];
296   const gchar *val = _g_getenv_nomalloc ("G_SLICE", buffer);
297   static const GDebugKey keys[] = {
298     { "always-malloc", 1 << 0 },
299     { "debug-blocks",  1 << 1 },
300   };
301   gint flags = !val ? 0 : g_parse_debug_string (val, keys, G_N_ELEMENTS (keys));
302   *config = slice_config;
303   if (flags & (1 << 0))         /* always-malloc */
304     config->always_malloc = TRUE;
305   if (flags & (1 << 1))         /* debug-blocks */
306     config->debug_blocks = TRUE;
307 }
308
309 static void
310 g_slice_init_nomessage (void)
311 {
312   /* we may not use g_error() or friends here */
313   mem_assert (sys_page_size == 0);
314   mem_assert (MIN_MAGAZINE_SIZE >= 4);
315
316 #ifdef G_OS_WIN32
317   {
318     SYSTEM_INFO system_info;
319     GetSystemInfo (&system_info);
320     sys_page_size = system_info.dwPageSize;
321   }
322 #else
323   sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */
324 #endif
325   mem_assert (sys_page_size >= 2 * LARGEALIGNMENT);
326   mem_assert ((sys_page_size & (sys_page_size - 1)) == 0);
327   slice_config_init (&allocator->config);
328   allocator->min_page_size = sys_page_size;
329 #if HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN
330   /* allow allocation of pages up to 8KB (with 8KB alignment).
331    * this is useful because many medium to large sized structures
332    * fit less than 8 times (see [4]) into 4KB pages.
333    * we allow very small page sizes here, to reduce wastage in
334    * threads if only small allocations are required (this does
335    * bear the risk of incresing allocation times and fragmentation
336    * though).
337    */
338   allocator->min_page_size = MAX (allocator->min_page_size, 4096);
339   allocator->max_page_size = MAX (allocator->min_page_size, 8192);
340   allocator->min_page_size = MIN (allocator->min_page_size, 128);
341 #else
342   /* we can only align to system page size */
343   allocator->max_page_size = sys_page_size;
344 #endif
345   allocator->magazine_mutex = NULL;     /* _g_slice_thread_init_nomessage() */
346   allocator->magazines = g_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator));
347   allocator->contention_counters = g_new0 (guint, MAX_SLAB_INDEX (allocator));
348   allocator->mutex_counter = 0;
349   allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */
350   allocator->last_stamp = 0;
351   allocator->slab_mutex = NULL;         /* _g_slice_thread_init_nomessage() */
352   allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator));
353   allocator->color_accu = 0;
354   magazine_cache_update_stamp();
355   /* values cached for performance reasons */
356   allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator);
357   if (allocator->config.always_malloc || allocator->config.bypass_magazines)
358     allocator->max_slab_chunk_size_for_magazine_cache = 0;      /* non-optimized cases */
359   /* at this point, g_mem_gc_friendly() should be initialized, this
360    * should have been accomplished by the above g_malloc/g_new calls
361    */
362 #ifdef G_OS_WIN32
363   if (allocator->config.debug_blocks)
364     InitializeCriticalSection (&sdt_mutex);
365 #endif
366 }
367
368 static inline guint
369 allocator_categorize (gsize aligned_chunk_size)
370 {
371   /* speed up the likely path */
372   if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache))
373     return 1;           /* use magazine cache */
374
375   /* the above will fail (max_slab_chunk_size_for_magazine_cache == 0) if the
376    * allocator is still uninitialized, or if we are not configured to use the
377    * magazine cache.
378    */
379   if (!sys_page_size)
380     g_slice_init_nomessage ();
381   if (!allocator->config.always_malloc &&
382       aligned_chunk_size &&
383       aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator))
384     {
385       if (allocator->config.bypass_magazines)
386         return 2;       /* use slab allocator, see [2] */
387       return 1;         /* use magazine cache */
388     }
389   return 0;             /* use malloc() */
390 }
391
392 void
393 _g_slice_thread_init_nomessage (void)
394 {
395   /* we may not use g_error() or friends here */
396   if (sys_page_size)
397     mem_error ("g_thread_init() must be called before GSlice is used, memory corrupted...");
398   g_slice_init_nomessage();
399   private_thread_memory = g_private_new (private_thread_memory_cleanup);
400   allocator->magazine_mutex = g_mutex_new();
401   allocator->slab_mutex = g_mutex_new();
402   if (allocator->config.debug_blocks)
403     smc_tree_mutex = g_mutex_new();
404 }
405
406 static inline void
407 g_mutex_lock_a (GMutex *mutex,
408                 guint  *contention_counter)
409 {
410   gboolean contention = FALSE;
411   if (!g_mutex_trylock (mutex))
412     {
413       g_mutex_lock (mutex);
414       contention = TRUE;
415     }
416   if (contention)
417     {
418       allocator->mutex_counter++;
419       if (allocator->mutex_counter >= 1)        /* quickly adapt to contention */
420         {
421           allocator->mutex_counter = 0;
422           *contention_counter = MIN (*contention_counter + 1, MAX_MAGAZINE_SIZE);
423         }
424     }
425   else /* !contention */
426     {
427       allocator->mutex_counter--;
428       if (allocator->mutex_counter < -11)       /* moderately recover magazine sizes */
429         {
430           allocator->mutex_counter = 0;
431           *contention_counter = MAX (*contention_counter, 1) - 1;
432         }
433     }
434 }
435
436 static inline ThreadMemory*
437 thread_memory_from_self (void)
438 {
439   ThreadMemory *tmem = g_private_get (private_thread_memory);
440   if (G_UNLIKELY (!tmem))
441     {
442       const guint n_magazines = MAX_SLAB_INDEX (allocator);
443       tmem = g_malloc0 (sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines);
444       tmem->magazine1 = (Magazine*) (tmem + 1);
445       tmem->magazine2 = &tmem->magazine1[n_magazines];
446       g_private_set (private_thread_memory, tmem);
447     }
448   return tmem;
449 }
450
451 static inline ChunkLink*
452 magazine_chain_pop_head (ChunkLink **magazine_chunks)
453 {
454   /* magazine chains are linked via ChunkLink->next.
455    * each ChunkLink->data of the toplevel chain may point to a subchain,
456    * linked via ChunkLink->next. ChunkLink->data of the subchains just
457    * contains uninitialized junk.
458    */
459   ChunkLink *chunk = (*magazine_chunks)->data;
460   if (G_UNLIKELY (chunk))
461     {
462       /* allocating from freed list */
463       (*magazine_chunks)->data = chunk->next;
464     }
465   else
466     {
467       chunk = *magazine_chunks;
468       *magazine_chunks = chunk->next;
469     }
470   return chunk;
471 }
472
473 #if 0 /* useful for debugging */
474 static guint
475 magazine_count (ChunkLink *head)
476 {
477   guint count = 0;
478   if (!head)
479     return 0;
480   while (head)
481     {
482       ChunkLink *child = head->data;
483       count += 1;
484       for (child = head->data; child; child = child->next)
485         count += 1;
486       head = head->next;
487     }
488   return count;
489 }
490 #endif
491
492 static inline gsize
493 allocator_get_magazine_threshold (Allocator *allocator,
494                                   guint      ix)
495 {
496   /* the magazine size calculated here has a lower bound of MIN_MAGAZINE_SIZE,
497    * which is required by the implementation. also, for moderately sized chunks
498    * (say >= 64 bytes), magazine sizes shouldn't be much smaller then the number
499    * of chunks available per page/2 to avoid excessive traffic in the magazine
500    * cache for small to medium sized structures.
501    * the upper bound of the magazine size is effectively provided by
502    * MAX_MAGAZINE_SIZE. for larger chunks, this number is scaled down so that
503    * the content of a single magazine doesn't exceed ca. 16KB.
504    */
505   gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
506   guint threshold = MAX (MIN_MAGAZINE_SIZE, allocator->max_page_size / MAX (5 * chunk_size, 5 * 32));
507   guint contention_counter = allocator->contention_counters[ix];
508   if (G_UNLIKELY (contention_counter))  /* single CPU bias */
509     {
510       /* adapt contention counter thresholds to chunk sizes */
511       contention_counter = contention_counter * 64 / chunk_size;
512       threshold = MAX (threshold, contention_counter);
513     }
514   return threshold;
515 }
516
517 /* --- magazine cache --- */
518 static inline void
519 magazine_cache_update_stamp (void)
520 {
521   if (allocator->stamp_counter >= MAX_STAMP_COUNTER)
522     {
523       GTimeVal tv;
524       g_get_current_time (&tv);
525       allocator->last_stamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* milli seconds */
526       allocator->stamp_counter = 0;
527     }
528   else
529     allocator->stamp_counter++;
530 }
531
532 static inline ChunkLink*
533 magazine_chain_prepare_fields (ChunkLink *magazine_chunks)
534 {
535   ChunkLink *chunk1;
536   ChunkLink *chunk2;
537   ChunkLink *chunk3;
538   ChunkLink *chunk4;
539   /* checked upon initialization: mem_assert (MIN_MAGAZINE_SIZE >= 4); */
540   /* ensure a magazine with at least 4 unused data pointers */
541   chunk1 = magazine_chain_pop_head (&magazine_chunks);
542   chunk2 = magazine_chain_pop_head (&magazine_chunks);
543   chunk3 = magazine_chain_pop_head (&magazine_chunks);
544   chunk4 = magazine_chain_pop_head (&magazine_chunks);
545   chunk4->next = magazine_chunks;
546   chunk3->next = chunk4;
547   chunk2->next = chunk3;
548   chunk1->next = chunk2;
549   return chunk1;
550 }
551
552 /* access the first 3 fields of a specially prepared magazine chain */
553 #define magazine_chain_prev(mc)         ((mc)->data)
554 #define magazine_chain_stamp(mc)        ((mc)->next->data)
555 #define magazine_chain_uint_stamp(mc)   GPOINTER_TO_UINT ((mc)->next->data)
556 #define magazine_chain_next(mc)         ((mc)->next->next->data)
557 #define magazine_chain_count(mc)        ((mc)->next->next->next->data)
558
559 static void
560 magazine_cache_trim (Allocator *allocator,
561                      guint      ix,
562                      guint      stamp)
563 {
564   /* g_mutex_lock (allocator->mutex); done by caller */
565   /* trim magazine cache from tail */
566   ChunkLink *current = magazine_chain_prev (allocator->magazines[ix]);
567   ChunkLink *trash = NULL;
568   while (ABS (stamp - magazine_chain_uint_stamp (current)) >= allocator->config.working_set_msecs)
569     {
570       /* unlink */
571       ChunkLink *prev = magazine_chain_prev (current);
572       ChunkLink *next = magazine_chain_next (current);
573       magazine_chain_next (prev) = next;
574       magazine_chain_prev (next) = prev;
575       /* clear special fields, put on trash stack */
576       magazine_chain_next (current) = NULL;
577       magazine_chain_count (current) = NULL;
578       magazine_chain_stamp (current) = NULL;
579       magazine_chain_prev (current) = trash;
580       trash = current;
581       /* fixup list head if required */
582       if (current == allocator->magazines[ix])
583         {
584           allocator->magazines[ix] = NULL;
585           break;
586         }
587       current = prev;
588     }
589   g_mutex_unlock (allocator->magazine_mutex);
590   /* free trash */
591   if (trash)
592     {
593       const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
594       g_mutex_lock (allocator->slab_mutex);
595       while (trash)
596         {
597           current = trash;
598           trash = magazine_chain_prev (current);
599           magazine_chain_prev (current) = NULL; /* clear special field */
600           while (current)
601             {
602               ChunkLink *chunk = magazine_chain_pop_head (&current);
603               slab_allocator_free_chunk (chunk_size, chunk);
604             }
605         }
606       g_mutex_unlock (allocator->slab_mutex);
607     }
608 }
609
610 static void
611 magazine_cache_push_magazine (guint      ix,
612                               ChunkLink *magazine_chunks,
613                               gsize      count) /* must be >= MIN_MAGAZINE_SIZE */
614 {
615   ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks);
616   ChunkLink *next, *prev;
617   g_mutex_lock (allocator->magazine_mutex);
618   /* add magazine at head */
619   next = allocator->magazines[ix];
620   if (next)
621     prev = magazine_chain_prev (next);
622   else
623     next = prev = current;
624   magazine_chain_next (prev) = current;
625   magazine_chain_prev (next) = current;
626   magazine_chain_prev (current) = prev;
627   magazine_chain_next (current) = next;
628   magazine_chain_count (current) = (gpointer) count;
629   /* stamp magazine */
630   magazine_cache_update_stamp();
631   magazine_chain_stamp (current) = GUINT_TO_POINTER (allocator->last_stamp);
632   allocator->magazines[ix] = current;
633   /* free old magazines beyond a certain threshold */
634   magazine_cache_trim (allocator, ix, allocator->last_stamp);
635   /* g_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */
636 }
637
638 static ChunkLink*
639 magazine_cache_pop_magazine (guint  ix,
640                              gsize *countp)
641 {
642   g_mutex_lock_a (allocator->magazine_mutex, &allocator->contention_counters[ix]);
643   if (!allocator->magazines[ix])
644     {
645       guint magazine_threshold = allocator_get_magazine_threshold (allocator, ix);
646       gsize i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
647       ChunkLink *chunk, *head;
648       g_mutex_unlock (allocator->magazine_mutex);
649       g_mutex_lock (allocator->slab_mutex);
650       head = slab_allocator_alloc_chunk (chunk_size);
651       head->data = NULL;
652       chunk = head;
653       for (i = 1; i < magazine_threshold; i++)
654         {
655           chunk->next = slab_allocator_alloc_chunk (chunk_size);
656           chunk = chunk->next;
657           chunk->data = NULL;
658         }
659       chunk->next = NULL;
660       g_mutex_unlock (allocator->slab_mutex);
661       *countp = i;
662       return head;
663     }
664   else
665     {
666       ChunkLink *current = allocator->magazines[ix];
667       ChunkLink *prev = magazine_chain_prev (current);
668       ChunkLink *next = magazine_chain_next (current);
669       /* unlink */
670       magazine_chain_next (prev) = next;
671       magazine_chain_prev (next) = prev;
672       allocator->magazines[ix] = next == current ? NULL : next;
673       g_mutex_unlock (allocator->magazine_mutex);
674       /* clear special fields and hand out */
675       *countp = (gsize) magazine_chain_count (current);
676       magazine_chain_prev (current) = NULL;
677       magazine_chain_next (current) = NULL;
678       magazine_chain_count (current) = NULL;
679       magazine_chain_stamp (current) = NULL;
680       return current;
681     }
682 }
683
684 /* --- thread magazines --- */
685 static void
686 private_thread_memory_cleanup (gpointer data)
687 {
688   ThreadMemory *tmem = data;
689   const guint n_magazines = MAX_SLAB_INDEX (allocator);
690   guint ix;
691   for (ix = 0; ix < n_magazines; ix++)
692     {
693       Magazine *mags[2];
694       guint j;
695       mags[0] = &tmem->magazine1[ix];
696       mags[1] = &tmem->magazine2[ix];
697       for (j = 0; j < 2; j++)
698         {
699           Magazine *mag = mags[j];
700           if (mag->count >= MIN_MAGAZINE_SIZE)
701             magazine_cache_push_magazine (ix, mag->chunks, mag->count);
702           else
703             {
704               const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
705               g_mutex_lock (allocator->slab_mutex);
706               while (mag->chunks)
707                 {
708                   ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks);
709                   slab_allocator_free_chunk (chunk_size, chunk);
710                 }
711               g_mutex_unlock (allocator->slab_mutex);
712             }
713         }
714     }
715   g_free (tmem);
716 }
717
718 static void
719 thread_memory_magazine1_reload (ThreadMemory *tmem,
720                                 guint         ix)
721 {
722   Magazine *mag = &tmem->magazine1[ix];
723   mem_assert (mag->chunks == NULL); /* ensure that we may reset mag->count */
724   mag->count = 0;
725   mag->chunks = magazine_cache_pop_magazine (ix, &mag->count);
726 }
727
728 static void
729 thread_memory_magazine2_unload (ThreadMemory *tmem,
730                                 guint         ix)
731 {
732   Magazine *mag = &tmem->magazine2[ix];
733   magazine_cache_push_magazine (ix, mag->chunks, mag->count);
734   mag->chunks = NULL;
735   mag->count = 0;
736 }
737
738 static inline void
739 thread_memory_swap_magazines (ThreadMemory *tmem,
740                               guint         ix)
741 {
742   Magazine xmag = tmem->magazine1[ix];
743   tmem->magazine1[ix] = tmem->magazine2[ix];
744   tmem->magazine2[ix] = xmag;
745 }
746
747 static inline gboolean
748 thread_memory_magazine1_is_empty (ThreadMemory *tmem,
749                                   guint         ix)
750 {
751   return tmem->magazine1[ix].chunks == NULL;
752 }
753
754 static inline gboolean
755 thread_memory_magazine2_is_full (ThreadMemory *tmem,
756                                  guint         ix)
757 {
758   return tmem->magazine2[ix].count >= allocator_get_magazine_threshold (allocator, ix);
759 }
760
761 static inline gpointer
762 thread_memory_magazine1_alloc (ThreadMemory *tmem,
763                                guint         ix)
764 {
765   Magazine *mag = &tmem->magazine1[ix];
766   ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks);
767   if (G_LIKELY (mag->count > 0))
768     mag->count--;
769   return chunk;
770 }
771
772 static inline void
773 thread_memory_magazine2_free (ThreadMemory *tmem,
774                               guint         ix,
775                               gpointer      mem)
776 {
777   Magazine *mag = &tmem->magazine2[ix];
778   ChunkLink *chunk = mem;
779   chunk->data = NULL;
780   chunk->next = mag->chunks;
781   mag->chunks = chunk;
782   mag->count++;
783 }
784
785 /* --- API functions --- */
786 gpointer
787 g_slice_alloc (gsize mem_size)
788 {
789   gsize chunk_size;
790   gpointer mem;
791   guint acat;
792   chunk_size = P2ALIGN (mem_size);
793   acat = allocator_categorize (chunk_size);
794   if (G_LIKELY (acat == 1))     /* allocate through magazine layer */
795     {
796       ThreadMemory *tmem = thread_memory_from_self();
797       guint ix = SLAB_INDEX (allocator, chunk_size);
798       if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
799         {
800           thread_memory_swap_magazines (tmem, ix);
801           if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
802             thread_memory_magazine1_reload (tmem, ix);
803         }
804       mem = thread_memory_magazine1_alloc (tmem, ix);
805     }
806   else if (acat == 2)           /* allocate through slab allocator */
807     {
808       g_mutex_lock (allocator->slab_mutex);
809       mem = slab_allocator_alloc_chunk (chunk_size);
810       g_mutex_unlock (allocator->slab_mutex);
811     }
812   else                          /* delegate to system malloc */
813     mem = g_malloc (mem_size);
814   if (G_UNLIKELY (allocator->config.debug_blocks))
815     smc_notify_alloc (mem, mem_size);
816   return mem;
817 }
818
819 gpointer
820 g_slice_alloc0 (gsize mem_size)
821 {
822   gpointer mem = g_slice_alloc (mem_size);
823   if (mem)
824     memset (mem, 0, mem_size);
825   return mem;
826 }
827
828 void
829 g_slice_free1 (gsize    mem_size,
830                gpointer mem_block)
831 {
832   gsize chunk_size = P2ALIGN (mem_size);
833   guint acat = allocator_categorize (chunk_size);
834   if (G_UNLIKELY (!mem_block))
835     return;
836   if (G_UNLIKELY (allocator->config.debug_blocks) &&
837       !smc_notify_free (mem_block, mem_size))
838     abort();
839   if (G_LIKELY (acat == 1))             /* allocate through magazine layer */
840     {
841       ThreadMemory *tmem = thread_memory_from_self();
842       guint ix = SLAB_INDEX (allocator, chunk_size);
843       if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
844         {
845           thread_memory_swap_magazines (tmem, ix);
846           if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
847             thread_memory_magazine2_unload (tmem, ix);
848         }
849       if (G_UNLIKELY (g_mem_gc_friendly))
850         memset (mem_block, 0, chunk_size);
851       thread_memory_magazine2_free (tmem, ix, mem_block);
852     }
853   else if (acat == 2)                   /* allocate through slab allocator */
854     {
855       if (G_UNLIKELY (g_mem_gc_friendly))
856         memset (mem_block, 0, chunk_size);
857       g_mutex_lock (allocator->slab_mutex);
858       slab_allocator_free_chunk (chunk_size, mem_block);
859       g_mutex_unlock (allocator->slab_mutex);
860     }
861   else                                  /* delegate to system malloc */
862     {
863       if (G_UNLIKELY (g_mem_gc_friendly))
864         memset (mem_block, 0, mem_size);
865       g_free (mem_block);
866     }
867 }
868
869 void
870 g_slice_free_chain_with_offset (gsize    mem_size,
871                                 gpointer mem_chain,
872                                 gsize    next_offset)
873 {
874   gpointer slice = mem_chain;
875   /* while the thread magazines and the magazine cache are implemented so that
876    * they can easily be extended to allow for free lists containing more free
877    * lists for the first level nodes, which would allow O(1) freeing in this
878    * function, the benefit of such an extension is questionable, because:
879    * - the magazine size counts will become mere lower bounds which confuses
880    *   the code adapting to lock contention;
881    * - freeing a single node to the thread magazines is very fast, so this
882    *   O(list_length) operation is multiplied by a fairly small factor;
883    * - memory usage histograms on larger applications seem to indicate that
884    *   the amount of released multi node lists is negligible in comparison
885    *   to single node releases.
886    * - the major performance bottle neck, namely g_private_get() or
887    *   g_mutex_lock()/g_mutex_unlock() has already been moved out of the
888    *   inner loop for freeing chained slices.
889    */
890   gsize chunk_size = P2ALIGN (mem_size);
891   guint acat = allocator_categorize (chunk_size);
892   if (G_LIKELY (acat == 1))             /* allocate through magazine layer */
893     {
894       ThreadMemory *tmem = thread_memory_from_self();
895       guint ix = SLAB_INDEX (allocator, chunk_size);
896       while (slice)
897         {
898           guint8 *current = slice;
899           slice = *(gpointer*) (current + next_offset);
900           if (G_UNLIKELY (allocator->config.debug_blocks) &&
901               !smc_notify_free (current, mem_size))
902             abort();
903           if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
904             {
905               thread_memory_swap_magazines (tmem, ix);
906               if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
907                 thread_memory_magazine2_unload (tmem, ix);
908             }
909           if (G_UNLIKELY (g_mem_gc_friendly))
910             memset (current, 0, chunk_size);
911           thread_memory_magazine2_free (tmem, ix, current);
912         }
913     }
914   else if (acat == 2)                   /* allocate through slab allocator */
915     {
916       g_mutex_lock (allocator->slab_mutex);
917       while (slice)
918         {
919           guint8 *current = slice;
920           slice = *(gpointer*) (current + next_offset);
921           if (G_UNLIKELY (allocator->config.debug_blocks) &&
922               !smc_notify_free (current, mem_size))
923             abort();
924           if (G_UNLIKELY (g_mem_gc_friendly))
925             memset (current, 0, chunk_size);
926           slab_allocator_free_chunk (chunk_size, current);
927         }
928       g_mutex_unlock (allocator->slab_mutex);
929     }
930   else                                  /* delegate to system malloc */
931     while (slice)
932       {
933         guint8 *current = slice;
934         slice = *(gpointer*) (current + next_offset);
935         if (G_UNLIKELY (allocator->config.debug_blocks) &&
936             !smc_notify_free (current, mem_size))
937           abort();
938         if (G_UNLIKELY (g_mem_gc_friendly))
939           memset (current, 0, mem_size);
940         g_free (current);
941       }
942 }
943
944 /* --- single page allocator --- */
945 static void
946 allocator_slab_stack_push (Allocator *allocator,
947                            guint      ix,
948                            SlabInfo  *sinfo)
949 {
950   /* insert slab at slab ring head */
951   if (!allocator->slab_stack[ix])
952     {
953       sinfo->next = sinfo;
954       sinfo->prev = sinfo;
955     }
956   else
957     {
958       SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev;
959       next->prev = sinfo;
960       prev->next = sinfo;
961       sinfo->next = next;
962       sinfo->prev = prev;
963     }
964   allocator->slab_stack[ix] = sinfo;
965 }
966
967 static gsize
968 allocator_aligned_page_size (Allocator *allocator,
969                              gsize      n_bytes)
970 {
971   gsize val = 1 << g_bit_storage (n_bytes - 1);
972   val = MAX (val, allocator->min_page_size);
973   return val;
974 }
975
976 static void
977 allocator_add_slab (Allocator *allocator,
978                     guint      ix,
979                     gsize      chunk_size)
980 {
981   ChunkLink *chunk;
982   SlabInfo *sinfo;
983   gsize addr, padding, n_chunks, color = 0;
984   gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
985   /* allocate 1 page for the chunks and the slab */
986   gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING);
987   guint8 *mem = aligned_memory;
988   guint i;
989   if (!mem)
990     {
991       const gchar *syserr = "unknown error";
992 #if HAVE_STRERROR
993       syserr = strerror (errno);
994 #endif
995       mem_error ("failed to allocate %u bytes (alignment: %u): %s\n",
996                  (guint) (page_size - NATIVE_MALLOC_PADDING), (guint) page_size, syserr);
997     }
998   /* mask page adress */
999   addr = ((gsize) mem / page_size) * page_size;
1000   /* assert alignment */
1001   mem_assert (aligned_memory == (gpointer) addr);
1002   /* basic slab info setup */
1003   sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE);
1004   sinfo->n_allocated = 0;
1005   sinfo->chunks = NULL;
1006   /* figure cache colorization */
1007   n_chunks = ((guint8*) sinfo - mem) / chunk_size;
1008   padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size;
1009   if (padding)
1010     {
1011       color = (allocator->color_accu * P2ALIGNMENT) % padding;
1012       allocator->color_accu += allocator->config.color_increment;
1013     }
1014   /* add chunks to free list */
1015   chunk = (ChunkLink*) (mem + color);
1016   sinfo->chunks = chunk;
1017   for (i = 0; i < n_chunks - 1; i++)
1018     {
1019       chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size);
1020       chunk = chunk->next;
1021     }
1022   chunk->next = NULL;   /* last chunk */
1023   /* add slab to slab ring */
1024   allocator_slab_stack_push (allocator, ix, sinfo);
1025 }
1026
1027 static gpointer
1028 slab_allocator_alloc_chunk (gsize chunk_size)
1029 {
1030   ChunkLink *chunk;
1031   guint ix = SLAB_INDEX (allocator, chunk_size);
1032   /* ensure non-empty slab */
1033   if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks)
1034     allocator_add_slab (allocator, ix, chunk_size);
1035   /* allocate chunk */
1036   chunk = allocator->slab_stack[ix]->chunks;
1037   allocator->slab_stack[ix]->chunks = chunk->next;
1038   allocator->slab_stack[ix]->n_allocated++;
1039   /* rotate empty slabs */
1040   if (!allocator->slab_stack[ix]->chunks)
1041     allocator->slab_stack[ix] = allocator->slab_stack[ix]->next;
1042   return chunk;
1043 }
1044
1045 static void
1046 slab_allocator_free_chunk (gsize    chunk_size,
1047                            gpointer mem)
1048 {
1049   ChunkLink *chunk;
1050   gboolean was_empty;
1051   guint ix = SLAB_INDEX (allocator, chunk_size);
1052   gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
1053   gsize addr = ((gsize) mem / page_size) * page_size;
1054   /* mask page adress */
1055   guint8 *page = (guint8*) addr;
1056   SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE);
1057   /* assert valid chunk count */
1058   mem_assert (sinfo->n_allocated > 0);
1059   /* add chunk to free list */
1060   was_empty = sinfo->chunks == NULL;
1061   chunk = (ChunkLink*) mem;
1062   chunk->next = sinfo->chunks;
1063   sinfo->chunks = chunk;
1064   sinfo->n_allocated--;
1065   /* keep slab ring partially sorted, empty slabs at end */
1066   if (was_empty)
1067     {
1068       /* unlink slab */
1069       SlabInfo *next = sinfo->next, *prev = sinfo->prev;
1070       next->prev = prev;
1071       prev->next = next;
1072       if (allocator->slab_stack[ix] == sinfo)
1073         allocator->slab_stack[ix] = next == sinfo ? NULL : next;
1074       /* insert slab at head */
1075       allocator_slab_stack_push (allocator, ix, sinfo);
1076     }
1077   /* eagerly free complete unused slabs */
1078   if (!sinfo->n_allocated)
1079     {
1080       /* unlink slab */
1081       SlabInfo *next = sinfo->next, *prev = sinfo->prev;
1082       next->prev = prev;
1083       prev->next = next;
1084       if (allocator->slab_stack[ix] == sinfo)
1085         allocator->slab_stack[ix] = next == sinfo ? NULL : next;
1086       /* free slab */
1087       allocator_memfree (page_size, page);
1088     }
1089 }
1090
1091 /* --- memalign implementation --- */
1092 #ifdef HAVE_MALLOC_H
1093 #include <malloc.h>             /* memalign() */
1094 #endif
1095
1096 /* from config.h:
1097  * define HAVE_POSIX_MEMALIGN           1 // if free(posix_memalign(3)) works, <stdlib.h>
1098  * define HAVE_COMPLIANT_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works for sizes != 2^n, <stdlib.h>
1099  * define HAVE_MEMALIGN                 1 // if free(memalign(3)) works, <malloc.h>
1100  * define HAVE_VALLOC                   1 // if free(valloc(3)) works, <stdlib.h> or <malloc.h>
1101  * if none is provided, we implement malloc(3)-based alloc-only page alignment
1102  */
1103
1104 #if !(HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC)
1105 static GTrashStack *compat_valloc_trash = NULL;
1106 #endif
1107
1108 static gpointer
1109 allocator_memalign (gsize alignment,
1110                     gsize memsize)
1111 {
1112   gpointer aligned_memory = NULL;
1113   gint err = ENOMEM;
1114 #if     HAVE_COMPLIANT_POSIX_MEMALIGN
1115   err = posix_memalign (&aligned_memory, alignment, memsize);
1116 #elif   HAVE_MEMALIGN
1117   errno = 0;
1118   aligned_memory = memalign (alignment, memsize);
1119   err = errno;
1120 #elif   HAVE_VALLOC
1121   errno = 0;
1122   aligned_memory = valloc (memsize);
1123   err = errno;
1124 #else
1125   /* simplistic non-freeing page allocator */
1126   mem_assert (alignment == sys_page_size);
1127   mem_assert (memsize <= sys_page_size);
1128   if (!compat_valloc_trash)
1129     {
1130       const guint n_pages = 16;
1131       guint8 *mem = malloc (n_pages * sys_page_size);
1132       err = errno;
1133       if (mem)
1134         {
1135           gint i = n_pages;
1136           guint8 *amem = (guint8*) ALIGN ((gsize) mem, sys_page_size);
1137           if (amem != mem)
1138             i--;        /* mem wasn't page aligned */
1139           while (--i >= 0)
1140             g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size);
1141         }
1142     }
1143   aligned_memory = g_trash_stack_pop (&compat_valloc_trash);
1144 #endif
1145   if (!aligned_memory)
1146     errno = err;
1147   return aligned_memory;
1148 }
1149
1150 static void
1151 allocator_memfree (gsize    memsize,
1152                    gpointer mem)
1153 {
1154 #if     HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC
1155   free (mem);
1156 #else
1157   mem_assert (memsize <= sys_page_size);
1158   g_trash_stack_push (&compat_valloc_trash, mem);
1159 #endif
1160 }
1161
1162 #include <stdio.h>
1163
1164 static void
1165 mem_error (const char *format,
1166            ...)
1167 {
1168   const char *pname;
1169   va_list args;
1170   /* at least, put out "MEMORY-ERROR", in case we segfault during the rest of the function */
1171   fputs ("\n***MEMORY-ERROR***: ", stderr);
1172   pname = g_get_prgname();
1173   fprintf (stderr, "%s[%u]: GSlice: ", pname ? pname : "", getpid());
1174   va_start (args, format);
1175   vfprintf (stderr, format, args);
1176   va_end (args);
1177   fputs ("\n", stderr);
1178   abort();
1179   _exit (1);
1180 }
1181
1182 /* --- g-slice memory checker tree --- */
1183 typedef size_t SmcKType;                /* key type */
1184 typedef size_t SmcVType;                /* value type */
1185 typedef struct {
1186   SmcKType key;
1187   SmcVType value;
1188 } SmcEntry;
1189 static void             smc_tree_insert      (SmcKType  key,
1190                                               SmcVType  value);
1191 static gboolean         smc_tree_lookup      (SmcKType  key,
1192                                               SmcVType *value_p);
1193 static gboolean         smc_tree_remove      (SmcKType  key);
1194
1195
1196 /* --- g-slice memory checker implementation --- */
1197 static void
1198 smc_notify_alloc (void   *pointer,
1199                   size_t  size)
1200 {
1201   size_t adress = (size_t) pointer;
1202   if (pointer)
1203     smc_tree_insert (adress, size);
1204 }
1205
1206 #if 0
1207 static void
1208 smc_notify_ignore (void *pointer)
1209 {
1210   size_t adress = (size_t) pointer;
1211   if (pointer)
1212     smc_tree_remove (adress);
1213 }
1214 #endif
1215
1216 static int
1217 smc_notify_free (void   *pointer,
1218                  size_t  size)
1219 {
1220   size_t adress = (size_t) pointer;
1221   if (!pointer)
1222     return 1; /* ignore */
1223   SmcVType real_size;
1224   gboolean found_one = smc_tree_lookup (adress, &real_size);
1225   if (!found_one)
1226     {
1227       fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%zu\n", pointer, size);
1228       return 0;
1229     }
1230   if (real_size != size && (real_size || size))
1231     {
1232       fprintf (stderr, "GSlice: MemChecker: attempt to release block with invalid size: %p size=%zu invalid-size=%zu\n", pointer, real_size, size);
1233       return 0;
1234     }
1235   if (!smc_tree_remove (adress))
1236     {
1237       fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%zu\n", pointer, size);
1238       return 0;
1239     }
1240   return 1; /* all fine */
1241 }
1242
1243 /* --- g-slice memory checker tree implementation --- */
1244 #define SMC_TRUNK_COUNT     (4093 /* 16381 */)          /* prime, to distribute trunk collisions (big, allocated just once) */
1245 #define SMC_BRANCH_COUNT    (511)                       /* prime, to distribute branch collisions */
1246 #define SMC_TRUNK_EXTENT    (SMC_BRANCH_COUNT * 2039)   /* key adress space per trunk, should distribute uniformly across BRANCH_COUNT */
1247 #define SMC_TRUNK_HASH(k)   ((k / SMC_TRUNK_EXTENT) % SMC_TRUNK_COUNT)  /* generate new trunk hash per megabyte (roughly) */
1248 #define SMC_BRANCH_HASH(k)  (k % SMC_BRANCH_COUNT)
1249
1250 typedef struct {
1251   SmcEntry    *entries;
1252   unsigned int n_entries;
1253 } SmcBranch;
1254
1255 static SmcBranch     **smc_tree_root = NULL;
1256
1257 static void
1258 smc_tree_abort (int errval)
1259 {
1260   const char *syserr = "unknown error";
1261 #if HAVE_STRERROR
1262   syserr = strerror (errval);
1263 #endif
1264   mem_error ("MemChecker: failure in debugging tree: %s", syserr);
1265 }
1266
1267 static inline SmcEntry*
1268 smc_tree_branch_grow_L (SmcBranch   *branch,
1269                         unsigned int index)
1270 {
1271   unsigned int old_size = branch->n_entries * sizeof (branch->entries[0]);
1272   unsigned int new_size = old_size + sizeof (branch->entries[0]);
1273   SmcEntry *entry;
1274   mem_assert (index <= branch->n_entries);
1275   branch->entries = (SmcEntry*) realloc (branch->entries, new_size);
1276   if (!branch->entries)
1277     smc_tree_abort (errno);
1278   entry = branch->entries + index;
1279   g_memmove (entry + 1, entry, (branch->n_entries - index) * sizeof (entry[0]));
1280   branch->n_entries += 1;
1281   return entry;
1282 }
1283
1284 static inline SmcEntry*
1285 smc_tree_branch_lookup_nearest_L (SmcBranch *branch,
1286                                   SmcKType   key)
1287 {
1288   unsigned int n_nodes = branch->n_entries, offs = 0;
1289   SmcEntry *check = branch->entries;
1290   int cmp = 0;
1291   while (offs < n_nodes)
1292     {
1293       unsigned int i = (offs + n_nodes) >> 1;
1294       check = branch->entries + i;
1295       cmp = key < check->key ? -1 : key != check->key;
1296       if (cmp == 0)
1297         return check;                   /* return exact match */
1298       else if (cmp < 0)
1299         n_nodes = i;
1300       else /* (cmp > 0) */
1301         offs = i + 1;
1302     }
1303   /* check points at last mismatch, cmp > 0 indicates greater key */
1304   return cmp > 0 ? check + 1 : check;   /* return insertion position for inexact match */
1305 }
1306
1307 static void
1308 smc_tree_insert (SmcKType key,
1309                  SmcVType value)
1310 {
1311   SDT_LOCK ();
1312   g_mutex_lock (smc_tree_mutex);
1313   unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key);
1314   if (!smc_tree_root)
1315     {
1316       smc_tree_root = calloc (SMC_TRUNK_COUNT, sizeof (smc_tree_root[0]));
1317       if (!smc_tree_root)
1318         smc_tree_abort (errno);
1319     }
1320   if (!smc_tree_root[ix0])
1321     {
1322       smc_tree_root[ix0] = calloc (SMC_BRANCH_COUNT, sizeof (smc_tree_root[0][0]));
1323       if (!smc_tree_root[ix0])
1324         smc_tree_abort (errno);
1325     }
1326   SmcEntry *entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key);
1327   if (!entry ||                                                                         /* need create */
1328       entry >= smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries ||   /* need append */
1329       entry->key != key)                                                                /* need insert */
1330     entry = smc_tree_branch_grow_L (&smc_tree_root[ix0][ix1], entry - smc_tree_root[ix0][ix1].entries);
1331   entry->key = key;
1332   entry->value = value;
1333   g_mutex_unlock (smc_tree_mutex);
1334   SDT_UNLOCK ();
1335 }
1336
1337 static gboolean
1338 smc_tree_lookup (SmcKType  key,
1339                  SmcVType *value_p)
1340 {
1341   unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key);
1342   gboolean found_one = FALSE;
1343   SDT_LOCK ();
1344   *value_p = 0;
1345   g_mutex_lock (smc_tree_mutex);
1346   SmcEntry *entry = NULL;
1347   if (smc_tree_root && smc_tree_root[ix0])
1348     {
1349       entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key);
1350       if (entry &&
1351           entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries &&
1352           entry->key == key)
1353         {
1354           found_one = TRUE;
1355           *value_p = entry->value;
1356         }
1357     }
1358   g_mutex_unlock (smc_tree_mutex);
1359   SDT_UNLOCK ();
1360   return found_one;
1361 }
1362
1363 static gboolean
1364 smc_tree_remove (SmcKType key)
1365 {
1366   unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key);
1367   gboolean found_one = FALSE;
1368   SDT_LOCK ();
1369   g_mutex_lock (smc_tree_mutex);
1370   if (smc_tree_root && smc_tree_root[ix0])
1371     {
1372       SmcEntry *entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key);
1373       if (entry &&
1374           entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries &&
1375           entry->key == key)
1376         {
1377           unsigned int i = entry - smc_tree_root[ix0][ix1].entries;
1378           smc_tree_root[ix0][ix1].n_entries -= 1;
1379           g_memmove (entry, entry + 1, (smc_tree_root[ix0][ix1].n_entries - i) * sizeof (entry[0]));
1380           if (!smc_tree_root[ix0][ix1].n_entries)
1381             {
1382               /* avoid useless pressure on the memory system */
1383               free (smc_tree_root[ix0][ix1].entries);
1384               smc_tree_root[ix0][ix1].entries = NULL;
1385             }
1386           found_one = TRUE;
1387         }
1388     }
1389   g_mutex_unlock (smc_tree_mutex);
1390   SDT_UNLOCK ();
1391   return found_one;
1392 }
1393
1394 #ifdef  G_ENABLE_DEBUG
1395 void
1396 g_slice_debug_tree_statistics (void)
1397 {
1398   SDT_LOCK ();
1399   g_mutex_lock (smc_tree_mutex);
1400   if (smc_tree_root)
1401     {
1402       unsigned int i, j, t = 0, o = 0, b = 0, su = 0, ex = 0, en = 4294967295u;
1403       double tf, bf;
1404       for (i = 0; i < SMC_TRUNK_COUNT; i++)
1405         if (smc_tree_root[i])
1406           {
1407             t++;
1408             for (j = 0; j < SMC_BRANCH_COUNT; j++)
1409               if (smc_tree_root[i][j].n_entries)
1410                 {
1411                   b++;
1412                   su += smc_tree_root[i][j].n_entries;
1413                   en = MIN (en, smc_tree_root[i][j].n_entries);
1414                   ex = MAX (ex, smc_tree_root[i][j].n_entries);
1415                 }
1416               else if (smc_tree_root[i][j].entries)
1417                 o++; /* formerly used, now empty */
1418           }
1419       en = b ? en : 0;
1420       tf = MAX (t, 1.0); // max(1) to be a valid divisor
1421       bf = MAX (b, 1.0); // max(1) to be a valid divisor
1422       fprintf (stderr, "GSlice: MemChecker: %u trunks, %u branches, %u old branches\n", t, b, o);
1423       fprintf (stderr, "GSlice: MemChecker: %f branches per trunk, %.2f%% utilization\n",
1424                b / tf,
1425                100.0 - (SMC_BRANCH_COUNT - b / tf) / (0.01 * SMC_BRANCH_COUNT));
1426       fprintf (stderr, "GSlice: MemChecker: %f entries per branch, %u minimum, %u maximum\n",
1427                su / bf, en, ex);
1428     }
1429   else
1430     fprintf (stderr, "GSlice: MemChecker: root=NULL\n");
1431   g_mutex_unlock (smc_tree_mutex);
1432   SDT_UNLOCK ();
1433   
1434   /* sample statistics (beast + GSLice + 24h scripted core & GUI activity):
1435    *  PID %CPU %MEM   VSZ  RSS      COMMAND
1436    * 8887 30.3 45.8 456068 414856   beast-0.7.1 empty.bse
1437    * $ cat /proc/8887/statm # total-program-size resident-set-size shared-pages text/code data/stack library dirty-pages
1438    * 114017 103714 2354 344 0 108676 0
1439    * $ cat /proc/8887/status 
1440    * Name:   beast-0.7.1
1441    * VmSize:   456068 kB
1442    * VmLck:         0 kB
1443    * VmRSS:    414856 kB
1444    * VmData:   434620 kB
1445    * VmStk:        84 kB
1446    * VmExe:      1376 kB
1447    * VmLib:     13036 kB
1448    * VmPTE:       456 kB
1449    * Threads:        3
1450    * (gdb) print g_slice_debug_tree_statistics ()
1451    * GSlice: MemChecker: 422 trunks, 213068 branches, 0 old branches
1452    * GSlice: MemChecker: 504.900474 branches per trunk, 98.81% utilization
1453    * GSlice: MemChecker: 4.965039 entries per branch, 1 minimum, 37 maximum
1454    */
1455 }
1456 #endif /* G_ENABLE_DEBUG */
1457
1458 #define __G_SLICE_C__
1459 #include "galiasdef.c"