added mem_error() and mem_assert() to test and handle errors without
[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 #define _XOPEN_SOURCE 600       /* posix_memalign() */
21 #include <stdlib.h>             /* posix_memalign() */
22 #include <string.h>
23 #include <errno.h>
24 #include "config.h"
25 #include "gmem.h"               /* gslice.h */
26 #include "gthreadinit.h"
27 #include "galias.h"
28 #include "glib.h"
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>             /* sysconf() */
31 #endif
32 #ifdef G_OS_WIN32
33 #include <windows.h>
34 #endif
35
36 /* the GSlice allocator is split up into 4 layers, roughly modelled after the slab
37  * allocator and magazine extensions as outlined in:
38  * + [Bonwick94] Jeff Bonwick, The slab allocator: An object-caching kernel
39  *   memory allocator. USENIX 1994, http://citeseer.ist.psu.edu/bonwick94slab.html
40  * + [Bonwick01] Bonwick and Jonathan Adams, Magazines and vmem: Extending the
41  *   slab allocator to many cpu's and arbitrary resources.
42  *   USENIX 2001, http://citeseer.ist.psu.edu/bonwick01magazines.html
43  * the layers are:
44  * - the thread magazines. for each (aligned) chunk size, a magazine (a list)
45  *   of recently freed and soon to be allocated chunks is maintained per thread.
46  *   this way, most alloc/free requests can be quickly satisfied from per-thread
47  *   free lists which only require one g_private_get() call to retrive the
48  *   thread handle.
49  * - the magazine cache. allocating and freeing chunks to/from threads only
50  *   occours at magazine sizes from a global depot of magazines. the depot
51  *   maintaines a 15 second working set of allocated magazines, so full
52  *   magazines are not allocated and released too often.
53  *   the chunk size dependent magazine sizes automatically adapt (within limits,
54  *   see [3]) to lock contention to properly scale performance across a variety
55  *   of SMP systems.
56  * - the slab allocator. this allocator allocates slabs (blocks of memory) close
57  *   to the system page size or multiples thereof which have to be page aligned.
58  *   the blocks are divided into smaller chunks which are used to satisfy
59  *   allocations from the upper layers. the space provided by the reminder of
60  *   the chunk size division is used for cache colorization (random distribution
61  *   of chunk addresses) to improve processor cache utilization. multiple slabs
62  *   with the same chunk size are kept in a partially sorted ring to allow O(1)
63  *   freeing and allocation of chunks (as long as the allocation of an entirely
64  *   new slab can be avoided).
65  * - the page allocator. on most modern systems, posix_memalign(3) or
66  *   memalign(3) should be available, so this is used to allocate blocks with
67  *   system page size based alignments and sizes or multiples thereof.
68  *   if no memalign variant is provided, valloc() is used instead and
69  *   block sizes are limited to the system page size (no multiples thereof).
70  *   as a fallback, on system without even valloc(), a malloc(3)-based page
71  *   allocator with alloc-only behaviour is used.
72  *
73  * NOTES:
74  * [1] some systems memalign(3) implementations may rely on boundary tagging for
75  *     the handed out memory chunks. to avoid excessive page-wise fragmentation,
76  *     we reserve 2 * sizeof (void*) per block size for the systems memalign(3),
77  *     specified in NATIVE_MALLOC_PADDING.
78  * [2] using the slab allocator alone already provides for a fast and efficient
79  *     allocator, it doesn't properly scale beyond single-threaded uses though.
80  *     also, the slab allocator implements eager free(3)-ing, i.e. does not
81  *     provide any form of caching or working set maintenance. so if used alone,
82  *     it's vulnerable to trashing for sequences of balanced (alloc, free) pairs
83  *     at certain thresholds.
84  * [3] magazine sizes are bound by an implementation specific minimum size and
85  *     a chunk size specific maximum to limit magazine storage sizes to roughly
86  *     16KB.
87  * [4] allocating ca. 8 chunks per block/page keeps a good balance between
88  *     external and internal fragmentation (<= 12.5%). [Bonwick94]
89  */
90
91 /* --- macros and constants --- */
92 #define LARGEALIGNMENT          (256)
93 #define P2ALIGNMENT             (2 * sizeof (gsize))                            /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */
94 #define ALIGN(size, base)       ((base) * (gsize) (((size) + (base) - 1) / (base)))
95 #define NATIVE_MALLOC_PADDING   P2ALIGNMENT                                     /* per-page padding left for native malloc(3) see [1] */
96 #define SLAB_INFO_SIZE          P2ALIGN (sizeof (SlabInfo) + NATIVE_MALLOC_PADDING)
97 #define MAX_MAGAZINE_SIZE       (256)                                           /* see [3] and allocator_get_magazine_threshold() for this */
98 #define MIN_MAGAZINE_SIZE       (4)
99 #define MAX_STAMP_COUNTER       (7)                                             /* distributes the load of gettimeofday() */
100 #define MAX_SLAB_CHUNK_SIZE(al) (((al)->max_page_size - SLAB_INFO_SIZE) / 8)    /* we want at last 8 chunks per page, see [4] */
101 #define MAX_SLAB_INDEX(al)      (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1)
102 #define SLAB_INDEX(al, asize)   ((asize) / P2ALIGNMENT - 1)                     /* asize must be P2ALIGNMENT aligned */
103 #define SLAB_CHUNK_SIZE(al, ix) (((ix) + 1) * P2ALIGNMENT)
104 #define SLAB_BPAGE_SIZE(al,csz) (8 * (csz) + SLAB_INFO_SIZE)
105
106 /* optimized version of ALIGN (size, P2ALIGNMENT) */
107 #if     GLIB_SIZEOF_SIZE_T * 2 == 8  /* P2ALIGNMENT */
108 #define P2ALIGN(size)   (((size) + 0x7) & ~(gsize) 0x7)
109 #elif   GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */
110 #define P2ALIGN(size)   (((size) + 0xf) & ~(gsize) 0xf)
111 #else
112 #define P2ALIGN(size)   ALIGN (size, P2ALIGNMENT)
113 #endif
114
115 /* special helpers to avoid gmessage.c dependency */
116 static void mem_error (const char *format, ...) G_GNUC_PRINTF (1,2);
117 #define mem_assert(cond)    do { if (G_LIKELY (cond)) ; else mem_error ("assertion failed: %s", #cond); } while (0)
118
119 /* --- structures --- */
120 typedef struct _ChunkLink      ChunkLink;
121 typedef struct _SlabInfo       SlabInfo;
122 typedef struct _CachedMagazine CachedMagazine;
123 struct _ChunkLink {
124   ChunkLink *next;
125   ChunkLink *data;
126 };
127 struct _SlabInfo {
128   ChunkLink *chunks;
129   guint n_allocated;
130   SlabInfo *next, *prev;
131 };
132 typedef struct {
133   ChunkLink *chunks;
134   gsize      count;                     /* approximative chunks list length */
135 } Magazine;
136 typedef struct {
137   Magazine   *magazine1;                /* array of MAX_SLAB_INDEX (allocator) */
138   Magazine   *magazine2;                /* array of MAX_SLAB_INDEX (allocator) */
139 } ThreadMemory;
140 typedef struct {
141   gboolean always_malloc;
142   gboolean bypass_magazines;
143   gsize    working_set_msecs;
144   guint    color_increment;
145 } SliceConfig;
146 typedef struct {
147   /* const after initialization */
148   gsize         min_page_size, max_page_size;
149   SliceConfig   config;
150   gsize         max_slab_chunk_size_for_magazine_cache;
151   /* magazine cache */
152   GMutex       *magazine_mutex;
153   ChunkLink   **magazines;                /* array of MAX_SLAB_INDEX (allocator) */
154   guint        *contention_counters;      /* array of MAX_SLAB_INDEX (allocator) */
155   gint          mutex_counter;
156   guint         stamp_counter;
157   guint         last_stamp;
158   /* slab allocator */
159   GMutex       *slab_mutex;
160   SlabInfo    **slab_stack;                /* array of MAX_SLAB_INDEX (allocator) */
161   guint        color_accu;
162 } Allocator;
163
164 /* --- prototypes --- */
165 static gpointer     slab_allocator_alloc_chunk       (gsize      chunk_size);
166 static void         slab_allocator_free_chunk        (gsize      chunk_size,
167                                                       gpointer   mem);
168 static void         private_thread_memory_cleanup    (gpointer   data);
169 static gpointer     allocator_memalign               (gsize      alignment,
170                                                       gsize      memsize);
171 static void         allocator_memfree                (gsize      memsize,
172                                                       gpointer   mem);
173 static inline void  magazine_cache_update_stamp      (void);
174 static inline gsize allocator_get_magazine_threshold (Allocator *allocator,
175                                                       guint      ix);
176
177 /* --- variables --- */
178 static GPrivate        *private_thread_memory = NULL;
179 static gsize            sys_page_size = 0;
180 static Allocator        allocator[1] = { { 0, }, };
181 static SliceConfig      slice_config = {
182   FALSE,        /* always_malloc */
183   FALSE,        /* bypass_magazines */
184   15 * 1000,    /* working_set_msecs */
185   1,            /* color increment, alt: 0x7fffffff */
186 };
187
188 /* --- auxillary funcitons --- */
189 void
190 g_slice_set_config (GSliceConfig ckey,
191                     gint64       value)
192 {
193   g_return_if_fail (sys_page_size == 0);
194   switch (ckey)
195     {
196     case G_SLICE_CONFIG_ALWAYS_MALLOC:
197       slice_config.always_malloc = value != 0;
198       break;
199     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
200       slice_config.bypass_magazines = value != 0;
201       break;
202     case G_SLICE_CONFIG_WORKING_SET_MSECS:
203       slice_config.working_set_msecs = value;
204       break;
205     case G_SLICE_CONFIG_COLOR_INCREMENT:
206       slice_config.color_increment = value;
207     default: ;
208     }
209 }
210
211 gint64
212 g_slice_get_config (GSliceConfig ckey)
213 {
214   switch (ckey)
215     {
216     case G_SLICE_CONFIG_ALWAYS_MALLOC:
217       return slice_config.always_malloc;
218     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
219       return slice_config.bypass_magazines;
220     case G_SLICE_CONFIG_WORKING_SET_MSECS:
221       return slice_config.working_set_msecs;
222     case G_SLICE_CONFIG_CHUNK_SIZES:
223       return MAX_SLAB_INDEX (allocator);
224     case G_SLICE_CONFIG_COLOR_INCREMENT:
225       return slice_config.color_increment;
226     default:
227       return 0;
228     }
229 }
230
231 gint64*
232 g_slice_get_config_state (GSliceConfig ckey,
233                           gint64       address,
234                           guint       *n_values)
235 {
236   guint i = 0;
237   g_return_val_if_fail (n_values != NULL, NULL);
238   *n_values = 0;
239   switch (ckey)
240     {
241       gint64 array[64];
242     case G_SLICE_CONFIG_CONTENTION_COUNTER:
243       array[i++] = SLAB_CHUNK_SIZE (allocator, address);
244       array[i++] = allocator->contention_counters[address];
245       array[i++] = allocator_get_magazine_threshold (allocator, address);
246       *n_values = i;
247       return g_memdup (array, sizeof (array[0]) * *n_values);
248     default:
249       return NULL;
250     }
251 }
252
253 static void
254 g_slice_init_nomessage (void)
255 {
256   /* we may not use g_error() or friends here */
257   mem_assert (sys_page_size == 0);
258   mem_assert (MIN_MAGAZINE_SIZE >= 4);
259
260 #ifdef G_OS_WIN32
261   {
262     SYSTEM_INFO system_info;
263     GetSystemInfo (&system_info);
264     sys_page_size = system_info.dwPageSize;
265   }
266 #else
267   sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */
268 #endif
269   mem_assert (sys_page_size >= 2 * LARGEALIGNMENT);
270   mem_assert ((sys_page_size & (sys_page_size - 1)) == 0);
271   allocator->config = slice_config;
272   allocator->min_page_size = sys_page_size;
273 #if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN
274   /* allow allocation of pages up to 8KB (with 8KB alignment).
275    * this is useful because many medium to large sized structures
276    * fit less than 8 times (see [4]) into 4KB pages.
277    * we allow very small page sizes here, to reduce wastage in
278    * threads if only small allocations are required (this does
279    * bear the risk of incresing allocation times and fragmentation
280    * though).
281    */
282   allocator->min_page_size = MAX (allocator->min_page_size, 4096);
283   allocator->max_page_size = MAX (allocator->min_page_size, 8192);
284   allocator->min_page_size = MIN (allocator->min_page_size, 128);
285 #else
286   /* we can only align to system page size */
287   allocator->max_page_size = sys_page_size;
288 #endif
289   allocator->magazine_mutex = NULL;     /* _g_slice_thread_init_nomessage() */
290   allocator->magazines = g_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator));
291   allocator->contention_counters = g_new0 (guint, MAX_SLAB_INDEX (allocator));
292   allocator->mutex_counter = 0;
293   allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */
294   allocator->last_stamp = 0;
295   allocator->slab_mutex = NULL;         /* _g_slice_thread_init_nomessage() */
296   allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator));
297   allocator->color_accu = 0;
298   magazine_cache_update_stamp();
299   /* values cached for performance reasons */
300   allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator);
301   if (allocator->config.always_malloc || allocator->config.bypass_magazines)
302     allocator->max_slab_chunk_size_for_magazine_cache = 0;      /* non-optimized cases */
303 }
304
305 static inline guint
306 allocator_categorize (gsize aligned_chunk_size)
307 {
308   /* speed up the likely path */
309   if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache))
310     return 1;           /* use magazine cache */
311
312   /* the above will fail (max_slab_chunk_size_for_magazine_cache == 0) if the
313    * allocator is still uninitialized, or if we are not configured to use the
314    * magazine cache.
315    */
316   if (!sys_page_size)
317     g_slice_init_nomessage ();
318   if (!allocator->config.always_malloc &&
319       aligned_chunk_size &&
320       aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator))
321     {
322       if (allocator->config.bypass_magazines)
323         return 2;       /* use slab allocator, see [2] */
324       return 1;         /* use magazine cache */
325     }
326   return 0;             /* use malloc() */
327 }
328
329 void
330 _g_slice_thread_init_nomessage (void)
331 {
332   /* we may not use g_error() or friends here */
333   if (!sys_page_size)
334     g_slice_init_nomessage();
335   private_thread_memory = g_private_new (private_thread_memory_cleanup);
336   allocator->magazine_mutex = g_mutex_new();
337   allocator->slab_mutex = g_mutex_new();
338 }
339
340 static inline void
341 g_mutex_lock_a (GMutex *mutex,
342                 guint  *contention_counter)
343 {
344   gboolean contention = FALSE;
345   if (!g_mutex_trylock (mutex))
346     {
347       g_mutex_lock (mutex);
348       contention = TRUE;
349     }
350   if (contention)
351     {
352       allocator->mutex_counter++;
353       if (allocator->mutex_counter >= 1)        /* quickly adapt to contention */
354         {
355           allocator->mutex_counter = 0;
356           *contention_counter = MIN (*contention_counter + 1, MAX_MAGAZINE_SIZE);
357         }
358     }
359   else /* !contention */
360     {
361       allocator->mutex_counter--;
362       if (allocator->mutex_counter < -11)       /* moderately recover magazine sizes */
363         {
364           allocator->mutex_counter = 0;
365           *contention_counter = MAX (*contention_counter, 1) - 1;
366         }
367     }
368 }
369
370 static inline ThreadMemory*
371 thread_memory_from_self (void)
372 {
373   ThreadMemory *tmem = g_private_get (private_thread_memory);
374   if (G_UNLIKELY (!tmem))
375     {
376       const guint n_magazines = MAX_SLAB_INDEX (allocator);
377       tmem = g_malloc0 (sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines);
378       tmem->magazine1 = (Magazine*) (tmem + 1);
379       tmem->magazine2 = &tmem->magazine1[n_magazines];
380       g_private_set (private_thread_memory, tmem);
381     }
382   return tmem;
383 }
384
385 static inline ChunkLink*
386 magazine_chain_pop_head (ChunkLink **magazine_chunks)
387 {
388   /* magazine chains are linked via ChunkLink->next.
389    * each ChunkLink->data of the toplevel chain may point to a subchain,
390    * linked via ChunkLink->next. ChunkLink->data of the subchains just
391    * contains uninitialized junk.
392    */
393   ChunkLink *chunk = (*magazine_chunks)->data;
394   if (G_UNLIKELY (chunk))
395     {
396       /* allocating from freed list */
397       (*magazine_chunks)->data = chunk->next;
398     }
399   else
400     {
401       chunk = *magazine_chunks;
402       *magazine_chunks = chunk->next;
403     }
404   return chunk;
405 }
406
407 #if 0 /* useful for debugging */
408 static guint
409 magazine_count (ChunkLink *head)
410 {
411   guint count = 0;
412   if (!head)
413     return 0;
414   while (head)
415     {
416       ChunkLink *child = head->data;
417       count += 1;
418       for (child = head->data; child; child = child->next)
419         count += 1;
420       head = head->next;
421     }
422   return count;
423 }
424 #endif
425
426 static inline gsize
427 allocator_get_magazine_threshold (Allocator *allocator,
428                                   guint      ix)
429 {
430   /* the magazine size calculated here has a lower bound of MIN_MAGAZINE_SIZE,
431    * which is required by the implementation. also, for moderately sized chunks
432    * (say >= 64 bytes), magazine sizes shouldn't be much smaller then the number
433    * of chunks available per page/2 to avoid excessive traffic in the magazine
434    * cache for small to medium sized structures.
435    * the upper bound of the magazine size is effectively provided by
436    * MAX_MAGAZINE_SIZE. for larger chunks, this number is scaled down so that
437    * the content of a single magazine doesn't exceed ca. 16KB.
438    */
439   gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
440   guint threshold = MAX (MIN_MAGAZINE_SIZE, allocator->max_page_size / MAX (5 * chunk_size, 5 * 32));
441   guint contention_counter = allocator->contention_counters[ix];
442   if (G_UNLIKELY (contention_counter))  /* single CPU bias */
443     {
444       /* adapt contention counter thresholds to chunk sizes */
445       contention_counter = contention_counter * 64 / chunk_size;
446       threshold = MAX (threshold, contention_counter);
447     }
448   return threshold;
449 }
450
451 /* --- magazine cache --- */
452 static inline void
453 magazine_cache_update_stamp (void)
454 {
455   if (allocator->stamp_counter >= MAX_STAMP_COUNTER)
456     {
457       GTimeVal tv;
458       g_get_current_time (&tv);
459       allocator->last_stamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* milli seconds */
460       allocator->stamp_counter = 0;
461     }
462   else
463     allocator->stamp_counter++;
464 }
465
466 static inline ChunkLink*
467 magazine_chain_prepare_fields (ChunkLink *magazine_chunks)
468 {
469   ChunkLink *chunk1;
470   ChunkLink *chunk2;
471   ChunkLink *chunk3;
472   ChunkLink *chunk4;
473   /* checked upon initialization: mem_assert (MIN_MAGAZINE_SIZE >= 4); */
474   /* ensure a magazine with at least 4 unused data pointers */
475   chunk1 = magazine_chain_pop_head (&magazine_chunks);
476   chunk2 = magazine_chain_pop_head (&magazine_chunks);
477   chunk3 = magazine_chain_pop_head (&magazine_chunks);
478   chunk4 = magazine_chain_pop_head (&magazine_chunks);
479   chunk4->next = magazine_chunks;
480   chunk3->next = chunk4;
481   chunk2->next = chunk3;
482   chunk1->next = chunk2;
483   return chunk1;
484 }
485
486 /* access the first 3 fields of a specially prepared magazine chain */
487 #define magazine_chain_prev(mc)         ((mc)->data)
488 #define magazine_chain_stamp(mc)        ((mc)->next->data)
489 #define magazine_chain_uint_stamp(mc)   GPOINTER_TO_UINT ((mc)->next->data)
490 #define magazine_chain_next(mc)         ((mc)->next->next->data)
491 #define magazine_chain_count(mc)        ((mc)->next->next->next->data)
492
493 static void
494 magazine_cache_trim (Allocator *allocator,
495                      guint      ix,
496                      guint      stamp)
497 {
498   /* g_mutex_lock (allocator->mutex); done by caller */
499   /* trim magazine cache from tail */
500   ChunkLink *current = magazine_chain_prev (allocator->magazines[ix]);
501   ChunkLink *trash = NULL;
502   while (ABS (stamp - magazine_chain_uint_stamp (current)) >= allocator->config.working_set_msecs)
503     {
504       /* unlink */
505       ChunkLink *prev = magazine_chain_prev (current);
506       ChunkLink *next = magazine_chain_next (current);
507       magazine_chain_next (prev) = next;
508       magazine_chain_prev (next) = prev;
509       /* clear special fields, put on trash stack */
510       magazine_chain_next (current) = NULL;
511       magazine_chain_count (current) = NULL;
512       magazine_chain_stamp (current) = NULL;
513       magazine_chain_prev (current) = trash;
514       trash = current;
515       /* fixup list head if required */
516       if (current == allocator->magazines[ix])
517         {
518           allocator->magazines[ix] = NULL;
519           break;
520         }
521       current = prev;
522     }
523   g_mutex_unlock (allocator->magazine_mutex);
524   /* free trash */
525   if (trash)
526     {
527       const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
528       g_mutex_lock (allocator->slab_mutex);
529       while (trash)
530         {
531           current = trash;
532           trash = magazine_chain_prev (current);
533           magazine_chain_prev (current) = NULL; /* clear special field */
534           while (current)
535             {
536               ChunkLink *chunk = magazine_chain_pop_head (&current);
537               slab_allocator_free_chunk (chunk_size, chunk);
538             }
539         }
540       g_mutex_unlock (allocator->slab_mutex);
541     }
542 }
543
544 static void
545 magazine_cache_push_magazine (guint      ix,
546                               ChunkLink *magazine_chunks,
547                               gsize      count) /* must be >= MIN_MAGAZINE_SIZE */
548 {
549   ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks);
550   ChunkLink *next, *prev;
551   g_mutex_lock (allocator->magazine_mutex);
552   /* add magazine at head */
553   next = allocator->magazines[ix];
554   if (next)
555     prev = magazine_chain_prev (next);
556   else
557     next = prev = current;
558   magazine_chain_next (prev) = current;
559   magazine_chain_prev (next) = current;
560   magazine_chain_prev (current) = prev;
561   magazine_chain_next (current) = next;
562   magazine_chain_count (current) = (gpointer) count;
563   /* stamp magazine */
564   magazine_cache_update_stamp();
565   magazine_chain_stamp (current) = GUINT_TO_POINTER (allocator->last_stamp);
566   allocator->magazines[ix] = current;
567   /* free old magazines beyond a certain threshold */
568   magazine_cache_trim (allocator, ix, allocator->last_stamp);
569   /* g_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */
570 }
571
572 static ChunkLink*
573 magazine_cache_pop_magazine (guint  ix,
574                              gsize *countp)
575 {
576   g_mutex_lock_a (allocator->magazine_mutex, &allocator->contention_counters[ix]);
577   if (!allocator->magazines[ix])
578     {
579       guint magazine_threshold = allocator_get_magazine_threshold (allocator, ix);
580       gsize i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
581       ChunkLink *current = NULL;
582       g_mutex_unlock (allocator->magazine_mutex);
583       g_mutex_lock (allocator->slab_mutex);
584       for (i = 0; i < magazine_threshold; i++)
585         {
586           ChunkLink *chunk = slab_allocator_alloc_chunk (chunk_size);
587           chunk->data = NULL;
588           chunk->next = current;
589           current = chunk;
590         }
591       g_mutex_unlock (allocator->slab_mutex);
592       *countp = i;
593       return current;
594     }
595   else
596     {
597       ChunkLink *current = allocator->magazines[ix];
598       ChunkLink *prev = magazine_chain_prev (current);
599       ChunkLink *next = magazine_chain_next (current);
600       /* unlink */
601       magazine_chain_next (prev) = next;
602       magazine_chain_prev (next) = prev;
603       allocator->magazines[ix] = next == current ? NULL : next;
604       g_mutex_unlock (allocator->magazine_mutex);
605       /* clear special fields and hand out */
606       *countp = (gsize) magazine_chain_count (current);
607       magazine_chain_prev (current) = NULL;
608       magazine_chain_next (current) = NULL;
609       magazine_chain_count (current) = NULL;
610       magazine_chain_stamp (current) = NULL;
611       return current;
612     }
613 }
614
615 /* --- thread magazines --- */
616 static void
617 private_thread_memory_cleanup (gpointer data)
618 {
619   ThreadMemory *tmem = data;
620   const guint n_magazines = MAX_SLAB_INDEX (allocator);
621   guint ix;
622   for (ix = 0; ix < n_magazines; ix++)
623     {
624       Magazine *mags[2];
625       guint j;
626       mags[0] = &tmem->magazine1[ix];
627       mags[1] = &tmem->magazine2[ix];
628       for (j = 0; j < 2; j++)
629         {
630           Magazine *mag = mags[j];
631           if (mag->count >= MIN_MAGAZINE_SIZE)
632             magazine_cache_push_magazine (ix, mag->chunks, mag->count);
633           else
634             {
635               const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix);
636               g_mutex_lock (allocator->slab_mutex);
637               while (mag->chunks)
638                 {
639                   ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks);
640                   slab_allocator_free_chunk (chunk_size, chunk);
641                 }
642               g_mutex_unlock (allocator->slab_mutex);
643             }
644         }
645     }
646   g_free (tmem);
647 }
648
649 static void
650 thread_memory_magazine1_reload (ThreadMemory *tmem,
651                                 guint         ix)
652 {
653   Magazine *mag = &tmem->magazine1[ix];
654   mem_assert (mag->chunks == NULL); /* ensure that we may reset mag->count */
655   mag->count = 0;
656   mag->chunks = magazine_cache_pop_magazine (ix, &mag->count);
657 }
658
659 static void
660 thread_memory_magazine2_unload (ThreadMemory *tmem,
661                                 guint         ix)
662 {
663   Magazine *mag = &tmem->magazine2[ix];
664   magazine_cache_push_magazine (ix, mag->chunks, mag->count);
665   mag->chunks = NULL;
666   mag->count = 0;
667 }
668
669 static inline void
670 thread_memory_swap_magazines (ThreadMemory *tmem,
671                               guint         ix)
672 {
673   Magazine xmag = tmem->magazine1[ix];
674   tmem->magazine1[ix] = tmem->magazine2[ix];
675   tmem->magazine2[ix] = xmag;
676 }
677
678 static inline gboolean
679 thread_memory_magazine1_is_empty (ThreadMemory *tmem,
680                                   guint         ix)
681 {
682   return tmem->magazine1[ix].chunks == NULL;
683 }
684
685 static inline gboolean
686 thread_memory_magazine2_is_full (ThreadMemory *tmem,
687                                  guint         ix)
688 {
689   return tmem->magazine2[ix].count >= allocator_get_magazine_threshold (allocator, ix);
690 }
691
692 static inline gpointer
693 thread_memory_magazine1_alloc (ThreadMemory *tmem,
694                                guint         ix)
695 {
696   Magazine *mag = &tmem->magazine1[ix];
697   ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks);
698   if (G_LIKELY (mag->count > 0))
699     mag->count--;
700   return chunk;
701 }
702
703 static inline void
704 thread_memory_magazine2_free (ThreadMemory *tmem,
705                               guint         ix,
706                               gpointer      mem)
707 {
708   Magazine *mag = &tmem->magazine2[ix];
709   ChunkLink *chunk = mem;
710   chunk->data = NULL;
711   chunk->next = mag->chunks;
712   mag->chunks = chunk;
713   mag->count++;
714 }
715
716 /* --- API functions --- */
717 gpointer
718 g_slice_alloc (gsize mem_size)
719 {
720   gsize chunk_size;
721   gpointer mem;
722   guint acat;
723   chunk_size = P2ALIGN (mem_size);
724   acat = allocator_categorize (chunk_size);
725   if (G_LIKELY (acat == 1))     /* allocate through magazine layer */
726     {
727       ThreadMemory *tmem = thread_memory_from_self();
728       guint ix = SLAB_INDEX (allocator, chunk_size);
729       if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
730         {
731           thread_memory_swap_magazines (tmem, ix);
732           if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))
733             thread_memory_magazine1_reload (tmem, ix);
734         }
735       mem = thread_memory_magazine1_alloc (tmem, ix);
736     }
737   else if (acat == 2)           /* allocate through slab allocator */
738     {
739       g_mutex_lock (allocator->slab_mutex);
740       mem = slab_allocator_alloc_chunk (chunk_size);
741       g_mutex_unlock (allocator->slab_mutex);
742     }
743   else                          /* delegate to system malloc */
744     mem = g_malloc (mem_size);
745   return mem;
746 }
747
748 gpointer
749 g_slice_alloc0 (gsize mem_size)
750 {
751   gpointer mem = g_slice_alloc (mem_size);
752   if (mem)
753     memset (mem, 0, mem_size);
754   return mem;
755 }
756
757 void
758 g_slice_free1 (gsize    mem_size,
759                gpointer mem_block)
760 {
761   gsize chunk_size = P2ALIGN (mem_size);
762   guint acat = allocator_categorize (chunk_size);
763   if (G_UNLIKELY (!mem_block))
764     /* pass */;
765   else if (G_LIKELY (acat == 1))        /* allocate through magazine layer */
766     {
767       ThreadMemory *tmem = thread_memory_from_self();
768       guint ix = SLAB_INDEX (allocator, chunk_size);
769       if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
770         {
771           thread_memory_swap_magazines (tmem, ix);
772           if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
773             thread_memory_magazine2_unload (tmem, ix);
774         }
775       thread_memory_magazine2_free (tmem, ix, mem_block);
776     }
777   else if (acat == 2)                   /* allocate through slab allocator */
778     {
779       g_mutex_lock (allocator->slab_mutex);
780       slab_allocator_free_chunk (chunk_size, mem_block);
781       g_mutex_unlock (allocator->slab_mutex);
782     }
783   else                                  /* delegate to system malloc */
784     g_free (mem_block);
785 }
786
787 void
788 g_slice_free_chain_with_offset (gsize    mem_size,
789                                 gpointer mem_chain,
790                                 gsize    next_offset)
791 {
792   gpointer slice = mem_chain;
793   /* while the thread magazines and the magazine cache are implemented so that
794    * they can easily be extended to allow for free lists containing more free
795    * lists for the first level nodes, which would allow O(1) freeing in this
796    * function, the benefit of such an extension is questionable, because:
797    * - the magazine size counts will become mere lower bounds which confuses
798    *   the code adapting to lock contention;
799    * - freeing a single node to the thread magazines is very fast, so this
800    *   O(list_length) operation is multiplied by a fairly small factor;
801    * - memory usage histograms on larger applications seem to indicate that
802    *   the amount of released multi node lists is negligible in comparison
803    *   to single node releases.
804    * - the major performance bottle neck, namely g_private_get() or
805    *   g_mutex_lock()/g_mutex_unlock() has already been moved out of the
806    *   inner loop for freeing chained slices.
807    */
808   gsize chunk_size = P2ALIGN (mem_size);
809   guint acat = allocator_categorize (chunk_size);
810   if (G_LIKELY (acat == 1))             /* allocate through magazine layer */
811     {
812       ThreadMemory *tmem = thread_memory_from_self();
813       guint ix = SLAB_INDEX (allocator, chunk_size);
814       while (slice)
815         {
816           guint8 *current = slice;
817           slice = *(gpointer*) (current + next_offset);
818           if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
819             {
820               thread_memory_swap_magazines (tmem, ix);
821               if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix)))
822                 thread_memory_magazine2_unload (tmem, ix);
823             }
824           thread_memory_magazine2_free (tmem, ix, current);
825         }
826     }
827   else if (acat == 2)                   /* allocate through slab allocator */
828     {
829       g_mutex_lock (allocator->slab_mutex);
830       while (slice)
831         {
832           guint8 *current = slice;
833           slice = *(gpointer*) (current + next_offset);
834           slab_allocator_free_chunk (chunk_size, current);
835         }
836       g_mutex_unlock (allocator->slab_mutex);
837     }
838   else                                  /* delegate to system malloc */
839     while (slice)
840       {
841         guint8 *current = slice;
842         slice = *(gpointer*) (current + next_offset);
843         g_free (current);
844       }
845 }
846
847 /* --- single page allocator --- */
848 static void
849 allocator_slab_stack_push (Allocator *allocator,
850                            guint      ix,
851                            SlabInfo  *sinfo)
852 {
853   /* insert slab at slab ring head */
854   if (!allocator->slab_stack[ix])
855     {
856       sinfo->next = sinfo;
857       sinfo->prev = sinfo;
858     }
859   else
860     {
861       SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev;
862       next->prev = sinfo;
863       prev->next = sinfo;
864       sinfo->next = next;
865       sinfo->prev = prev;
866     }
867   allocator->slab_stack[ix] = sinfo;
868 }
869
870 static gsize
871 allocator_aligned_page_size (Allocator *allocator,
872                              gsize      n_bytes)
873 {
874   gsize val = 1 << g_bit_storage (n_bytes - 1);
875   val = MAX (val, allocator->min_page_size);
876   return val;
877 }
878
879 static void
880 allocator_add_slab (Allocator *allocator,
881                     guint      ix,
882                     gsize      chunk_size)
883 {
884   ChunkLink *chunk;
885   SlabInfo *sinfo;
886   gsize addr, padding, n_chunks, color = 0;
887   gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
888   /* allocate 1 page for the chunks and the slab */
889   gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING);
890   guint8 *mem = aligned_memory;
891   guint i;
892   if (!mem)
893     {
894       const gchar *syserr = "unknown error";
895 #if HAVE_STRERROR
896       syserr = strerror (errno);
897 #endif
898       mem_error ("failed to allocate %u bytes (alignment: %u): %s\n",
899                  (guint) (page_size - NATIVE_MALLOC_PADDING), (guint) page_size, syserr);
900     }
901   /* mask page adress */
902   addr = ((gsize) mem / page_size) * page_size;
903   /* assert alignment */
904   mem_assert (aligned_memory == (gpointer) addr);
905   /* basic slab info setup */
906   sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE);
907   sinfo->n_allocated = 0;
908   sinfo->chunks = NULL;
909   /* figure cache colorization */
910   n_chunks = ((guint8*) sinfo - mem) / chunk_size;
911   padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size;
912   if (padding)
913     {
914       color = (allocator->color_accu * P2ALIGNMENT) % padding;
915       allocator->color_accu += allocator->config.color_increment;
916     }
917   /* add chunks to free list */
918   chunk = (ChunkLink*) (mem + color);
919   sinfo->chunks = chunk;
920   for (i = 0; i < n_chunks - 1; i++)
921     {
922       chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size);
923       chunk = chunk->next;
924     }
925   chunk->next = NULL;   /* last chunk */
926   /* add slab to slab ring */
927   allocator_slab_stack_push (allocator, ix, sinfo);
928 }
929
930 static gpointer
931 slab_allocator_alloc_chunk (gsize chunk_size)
932 {
933   ChunkLink *chunk;
934   guint ix = SLAB_INDEX (allocator, chunk_size);
935   /* ensure non-empty slab */
936   if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks)
937     allocator_add_slab (allocator, ix, chunk_size);
938   /* allocate chunk */
939   chunk = allocator->slab_stack[ix]->chunks;
940   allocator->slab_stack[ix]->chunks = chunk->next;
941   allocator->slab_stack[ix]->n_allocated++;
942   /* rotate empty slabs */
943   if (!allocator->slab_stack[ix]->chunks)
944     allocator->slab_stack[ix] = allocator->slab_stack[ix]->next;
945   return chunk;
946 }
947
948 static void
949 slab_allocator_free_chunk (gsize    chunk_size,
950                            gpointer mem)
951 {
952   ChunkLink *chunk;
953   gboolean was_empty;
954   guint ix = SLAB_INDEX (allocator, chunk_size);
955   gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
956   gsize addr = ((gsize) mem / page_size) * page_size;
957   /* mask page adress */
958   guint8 *page = (guint8*) addr;
959   SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE);
960   /* assert valid chunk count */
961   mem_assert (sinfo->n_allocated > 0);
962   /* add chunk to free list */
963   was_empty = sinfo->chunks == NULL;
964   chunk = (ChunkLink*) mem;
965   chunk->next = sinfo->chunks;
966   sinfo->chunks = chunk;
967   sinfo->n_allocated--;
968   /* keep slab ring partially sorted, empty slabs at end */
969   if (was_empty)
970     {
971       /* unlink slab */
972       SlabInfo *next = sinfo->next, *prev = sinfo->prev;
973       next->prev = prev;
974       prev->next = next;
975       if (allocator->slab_stack[ix] == sinfo)
976         allocator->slab_stack[ix] = next == sinfo ? NULL : next;
977       /* insert slab at head */
978       allocator_slab_stack_push (allocator, ix, sinfo);
979     }
980   /* eagerly free complete unused slabs */
981   if (!sinfo->n_allocated)
982     {
983       /* unlink slab */
984       SlabInfo *next = sinfo->next, *prev = sinfo->prev;
985       next->prev = prev;
986       prev->next = next;
987       if (allocator->slab_stack[ix] == sinfo)
988         allocator->slab_stack[ix] = next == sinfo ? NULL : next;
989       /* free slab */
990       allocator_memfree (page_size, page);
991     }
992 }
993
994 /* --- memalign implementation --- */
995 #include <malloc.h>             /* memalign() */
996
997 /* from config.h:
998  * define HAVE_POSIX_MEMALIGN     1     // if free(posix_memalign(3)) works, <stdlib.h>
999  * define HAVE_MEMALIGN           1     // if free(memalign(3)) works, <malloc.h>
1000  * define HAVE_VALLOC             1     // if free(valloc(3)) works, <stdlib.h> or <malloc.h>
1001  * if none is provided, we implement malloc(3)-based alloc-only page alignment
1002  */
1003
1004 #if !(HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC)
1005 static GTrashStack *compat_valloc_trash = NULL;
1006 #endif
1007
1008 static gpointer
1009 allocator_memalign (gsize alignment,
1010                     gsize memsize)
1011 {
1012   gpointer aligned_memory = NULL;
1013   gint err = ENOMEM;
1014 #if     HAVE_POSIX_MEMALIGN
1015   err = posix_memalign (&aligned_memory, alignment, memsize);
1016 #elif   HAVE_MEMALIGN
1017   errno = 0;
1018   aligned_memory = memalign (alignment, memsize);
1019   err = errno;
1020 #elif   HAVE_VALLOC
1021   errno = 0;
1022   aligned_memory = valloc (memsize);
1023   err = errno;
1024 #else
1025   /* simplistic non-freeing page allocator */
1026   mem_assert (alignment == sys_page_size);
1027   mem_assert (memsize <= sys_page_size);
1028   if (!compat_valloc_trash)
1029     {
1030       const guint n_pages = 16;
1031       guint8 *mem = malloc (n_pages * sys_page_size);
1032       err = errno;
1033       if (mem)
1034         {
1035           gint i = n_pages;
1036           guint8 *amem = (guint8*) ALIGN ((gsize) mem, sys_page_size);
1037           if (amem != mem)
1038             i--;        /* mem wasn't page aligned */
1039           while (--i >= 0)
1040             g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size);
1041         }
1042     }
1043   aligned_memory = g_trash_stack_pop (&compat_valloc_trash);
1044 #endif
1045   if (!aligned_memory)
1046     errno = err;
1047   return aligned_memory;
1048 }
1049
1050 static void
1051 allocator_memfree (gsize    memsize,
1052                    gpointer mem)
1053 {
1054 #if     HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC
1055   free (mem);
1056 #else
1057   mem_assert (memsize <= sys_page_size);
1058   g_trash_stack_push (&compat_valloc_trash, mem);
1059 #endif
1060 }
1061
1062 #include <stdio.h>
1063
1064 static void
1065 mem_error (const char *format,
1066            ...)
1067 {
1068   const char *pname;
1069   va_list args;
1070   /* at least, put out "MEMORY-ERROR", in case we segfault during the rest of the function */
1071   fputs ("\n***MEMORY-ERROR***: ", stderr);
1072   pname = g_get_prgname();
1073   fprintf (stderr, "%s[%u]: GSlice: ", pname ? pname : "", getpid());
1074   va_start (args, format);
1075   vfprintf (stderr, format, args);
1076   va_end (args);
1077   fputs ("\n", stderr);
1078   _exit (1);
1079 }
1080
1081 #define __G_SLICE_C__
1082 #include "galiasdef.c"