Add to git.
[c2lib.git] / pool.c
1 /* An Apache-like pool allocator.
2  * By Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: pool.c,v 1.8 2002/10/13 12:25:42 rich Exp $
19  */
20
21 #include "config.h"
22
23 #if SIZEOF_VOID_P != SIZEOF_LONG
24 #error "This library currently assumes that sizeof (void *) == sizeof (long), which is not the case on your platform. This may cause the library to crash at runtime."
25 #endif
26
27 /* If set, then calls to pmalloc will initialise the memory to 0xefefef...,
28  * helping to catch uninitialised memory problems. This is very useful for
29  * debugging new code, but should be turned off on production systems.
30  */
31 #define DEBUG_UNINITIALISED_MEMORY 1
32
33 #include <stdio.h>
34 #include <stdlib.h>
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #ifdef HAVE_STRING_H
41 #include <string.h>
42 #endif
43
44 #ifdef HAVE_FCNTL_H
45 #include <fcntl.h>
46 #endif
47
48 #include <pool.h>
49
50 struct _pool_allocs
51 {
52   struct _pool_allocs *next;
53
54   /* The flags field contains:
55    * bit  31    == if set, this structure shouldn't be freed
56    * bits 30-16 == number of slots in the structure
57    * bits 15- 0 == number of slots used in the structure.
58    */
59   unsigned flags;
60
61 #define _PA_NO_FREE(pa) ((pa)->flags & 0x80000000U)
62 #define _PA_SLOTS(pa) (((pa)->flags & 0x7fff0000U) >> 16)
63 #define _PA_SLOTS_USED(pa) ((pa)->flags & 0xffffU)
64
65   void *slot[0];
66 } __attribute__((packed));
67
68 struct _pool_cleanup_slot
69 {
70   void (*fn) (void *);
71   void *data;
72 };
73
74 struct _pool_cleanups
75 {
76   struct _pool_cleanups *next;
77
78   /* The flags field contains:
79    * bit  31    == if set, this structure shouldn't be freed
80    * bits 30-16 == number of slots in the structure
81    * bits 15- 0 == number of slots used in the structure.
82    */
83   unsigned flags;
84
85 #define _PC_NO_FREE(pc) ((pc)->flags & 0x80000000U)
86 #define _PC_SLOTS(pc) (((pc)->flags & 0x7fff0000U) >> 16)
87 #define _PC_SLOTS_USED(pc) ((pc)->flags & 0xffffU)
88
89   struct _pool_cleanup_slot slot[0];
90 } __attribute__((packed));
91
92 #define INITIAL_PA_SLOTS 16U
93 #define MAX_PA_SLOTS     16384U /* Must be <= 16384 */
94 #define INITIAL_PC_SLOTS 2U
95 #define MAX_PC_SLOTS     16384U /* Must be <= 16384 */
96
97 struct pool
98 {
99   /* If this is a subpool, then this points to the parent. */
100   struct pool *parent_pool;
101
102   /* When subpools are stored on a list, this is used to link the list. */
103   struct pool *next;
104
105   /* Sub-pools. */
106   struct pool *subpool_list;
107
108   /* Pointer to head block of memory allocations. */
109   struct _pool_allocs *allocs;
110
111   /* Pointer to head block of clean-up functions. */
112   struct _pool_cleanups *cleanups;
113 };
114
115 #ifndef NO_GLOBAL_POOL
116 pool global_pool;
117 #endif
118
119 static int trace_fd = -1;
120 static const char *trace_filename = 0;
121
122 static void (*bad_malloc_handler) (void) = abort;
123 #ifndef NO_GLOBAL_POOL
124 static void alloc_global_pool (void) __attribute__((constructor));
125 static void free_global_pool (void) __attribute__((destructor));
126 #endif
127 static void open_trace_file (void) __attribute__((constructor));
128 static void trace (const char *fn, void *caller, struct pool *ptr1, void *ptr2, void *ptr3, int i1);
129
130 #define TRACE(ptr1, ptr2, ptr3, i1) do { if (trace_filename) trace (__PRETTY_FUNCTION__, __builtin_return_address (0), (ptr1), (ptr2), (ptr3), (i1)); } while (0)
131
132 pool
133 new_pool ()
134 {
135   /* The amount of space required for pool + allocs + cleanups. */
136   const int size
137     = sizeof (struct pool) +
138     sizeof (struct _pool_allocs) +
139     INITIAL_PA_SLOTS * sizeof (void *) +
140     sizeof (struct _pool_cleanups) +
141     INITIAL_PC_SLOTS * sizeof (struct _pool_cleanup_slot);
142
143   pool p = malloc (size);
144   if (p == 0) bad_malloc_handler ();
145
146   memset (p, 0, size);
147
148   p->allocs = (struct _pool_allocs *) ((void *)p + sizeof (struct pool));
149   p->cleanups = (struct _pool_cleanups *)
150     ((void *)p + sizeof (struct pool) + sizeof (struct _pool_allocs)
151      + INITIAL_PA_SLOTS * sizeof (void *));
152
153   p->allocs->flags = 0x80000000U | INITIAL_PA_SLOTS << 16;
154   p->cleanups->flags = 0x80000000U | INITIAL_PC_SLOTS << 16;
155
156   TRACE (p, 0, 0, 0);
157
158   return p;
159 }
160
161 pool
162 new_subpool (pool parent)
163 {
164   pool p = new_pool ();
165   p->parent_pool = parent;
166
167   p->next = parent->subpool_list;
168   parent->subpool_list = p;
169
170   TRACE (p, parent, 0, 0);
171
172   return p;
173 }
174
175 static inline void
176 _do_cleanups (pool p)
177 {
178   struct _pool_cleanups *pc, *pc_next;
179   int i;
180
181   for (pc = p->cleanups; pc; pc = pc_next)
182     {
183       pc_next = pc->next;
184
185       for (i = 0; i < _PC_SLOTS_USED (pc); ++i)
186         pc->slot[i].fn (pc->slot[i].data);
187       if (!_PC_NO_FREE (pc))
188         free (pc);
189     }
190 }
191
192 static inline void
193 _do_frees (pool p)
194 {
195   struct _pool_allocs *pa, *pa_next;
196   int i;
197
198   for (pa = p->allocs; pa; pa = pa_next)
199     {
200       pa_next = pa->next;
201
202       for (i = 0; i < _PA_SLOTS_USED (pa); ++i)
203         free (pa->slot[i]);
204       if (!_PA_NO_FREE (pa))
205         free (pa);
206     }
207 }
208
209 void
210 delete_pool (pool p)
211 {
212   _do_cleanups (p);
213
214   /* Clean up any sub-pools. */
215   while (p->subpool_list) delete_pool (p->subpool_list);
216
217   _do_frees (p);
218
219   /* Do I have a parent? If so, remove myself from my parent's subpool
220    * list.
221    */
222   if (p->parent_pool)
223     {
224       pool parent = p->parent_pool, this, last = 0;
225
226       for (this = parent->subpool_list; this; last = this, this = this->next)
227         {
228           if (this == p)
229             {
230               /* Remove this one. */
231               if (last != 0)
232                 last->next = this->next;
233               else
234                 parent->subpool_list = this->next;
235
236               goto found_me;
237             }
238         }
239
240       abort ();                 /* Oops - self not found on subpool list. */
241     found_me:;
242     }
243
244   free (p);
245
246   TRACE (p, 0, 0, 0);
247 }
248
249 void *
250 pmalloc (pool p, size_t n)
251 {
252   void *ptr;
253
254   ptr = malloc (n);
255   if (ptr == 0) bad_malloc_handler ();
256
257 #if DEBUG_UNINITIALISED_MEMORY
258   memset (ptr, 0xef, n);
259 #endif
260
261   pool_register_malloc (p, ptr);
262
263   TRACE (p, ptr, 0, n);
264
265   return ptr;
266 }
267
268 void *
269 pcalloc (pool p, size_t nmemb, size_t size)
270 {
271   void *ptr = pmalloc (p, nmemb * size);
272   if (ptr) memset (ptr, 0, nmemb * size);
273   return ptr;
274 }
275
276 void *
277 prealloc (pool p, void *ptr, size_t n)
278 {
279   struct _pool_allocs *pa;
280   int i;
281   void *new_ptr;
282
283   if (ptr == 0)
284     return pmalloc (p, n);
285
286   new_ptr = realloc (ptr, n);
287   if (new_ptr == 0) bad_malloc_handler ();
288
289   /* XXX This is inefficient. We need to search through the
290    * allocations to find this one and update the pointer.
291    */
292   if (ptr != new_ptr)
293     {
294       for (pa = p->allocs; pa; pa = pa->next)
295         {
296           for (i = 0; i < _PA_SLOTS_USED (pa); ++i)
297             if (pa->slot[i] == ptr)
298               {
299                 pa->slot[i] = new_ptr;
300                 goto found;
301               }
302         }
303       abort ();
304
305     found:;
306     }
307
308   TRACE (p, ptr, new_ptr, n);
309
310   return new_ptr;
311 }
312
313 void
314 pool_register_cleanup_fn (pool p, void (*fn) (void *), void *data)
315 {
316   unsigned nr_slots;
317   struct _pool_cleanups *pc;
318
319   if (_PC_SLOTS_USED (p->cleanups) < _PC_SLOTS (p->cleanups))
320     {
321     again:
322       p->cleanups->slot[_PC_SLOTS_USED(p->cleanups)].fn = fn;
323       p->cleanups->slot[_PC_SLOTS_USED(p->cleanups)].data = data;
324       p->cleanups->flags++;
325       return;
326     }
327
328   /* Allocate a new block of cleanup slots. */
329   nr_slots = _PC_SLOTS (p->cleanups);
330   if (nr_slots < MAX_PC_SLOTS)
331     nr_slots *= 2;
332
333   pc = malloc (sizeof (struct _pool_cleanups) +
334                nr_slots * sizeof (struct _pool_cleanup_slot));
335   if (pc == 0) bad_malloc_handler ();
336   pc->next = p->cleanups;
337   pc->flags = nr_slots << 16;
338   p->cleanups = pc;
339
340   goto again;
341 }
342
343 void
344 pool_register_malloc (pool p, void *ptr)
345 {
346   unsigned nr_slots;
347   struct _pool_allocs *pa;
348
349   if (_PA_SLOTS_USED (p->allocs) < _PA_SLOTS (p->allocs))
350     {
351     again:
352       p->allocs->slot[_PA_SLOTS_USED(p->allocs)] = ptr;
353       p->allocs->flags++;
354       return;
355     }
356
357   /* Allocate a new block of slots. */
358   nr_slots = _PA_SLOTS (p->allocs);
359   if (nr_slots < MAX_PA_SLOTS)
360     nr_slots *= 2;
361
362   pa = malloc (sizeof (struct _pool_allocs) + nr_slots * sizeof (void *));
363   if (pa == 0) bad_malloc_handler ();
364   pa->next = p->allocs;
365   pa->flags = nr_slots << 16;
366   p->allocs = pa;
367
368   goto again;
369 }
370
371 static void
372 _pool_close (void *fdv)
373 {
374   long fd = (long) fdv;
375   close (fd);
376 }
377
378 void
379 pool_register_fd (pool p, int fd)
380 {
381   pool_register_cleanup_fn (p, _pool_close, (void *) (long) fd);
382 }
383
384 #ifndef NO_GLOBAL_POOL
385 static void
386 alloc_global_pool ()
387 {
388   global_pool = new_pool ();
389 }
390
391 static void
392 free_global_pool ()
393 {
394   delete_pool (global_pool);
395 }
396 #endif /* !NO_GLOBAL_POOL */
397
398 void (*
399 pool_set_bad_malloc_handler (void (*fn) (void))) (void)
400 {
401   void (*old_fn) (void) = bad_malloc_handler;
402   bad_malloc_handler = fn;
403   return old_fn;
404 }
405
406 static int
407 _get_struct_size (const pool p)
408 {
409   pool subpool;
410   struct _pool_allocs *pa;
411   struct _pool_cleanups *pc;
412   int size;
413
414   size = sizeof (*p);
415
416   for (pa = p->allocs; pa; pa = pa->next)
417     size += sizeof (struct _pool_allocs)
418             + _PA_SLOTS (pa) * sizeof (void *);
419
420   for (pc = p->cleanups; pc; pc = pc->next)
421     size += sizeof (struct _pool_cleanups)
422             + _PC_SLOTS (pc) * sizeof (struct _pool_cleanup_slot);
423
424   for (subpool = p->subpool_list; subpool; subpool = subpool->next)
425     size += _get_struct_size (subpool);
426
427   return size;
428 }
429
430 static int
431 _get_nr_subpools (const pool p)
432 {
433   pool subpool;
434   int count = 1;
435
436   for (subpool = p->subpool_list; subpool; subpool = subpool->next)
437     count += _get_nr_subpools (subpool);
438
439   return count;
440 }
441
442 void
443 pool_get_stats (const pool p, struct pool_stats *stats, size_t n)
444 {
445   struct pool_stats s;
446
447   s.nr_subpools = _get_nr_subpools (p);
448   s.struct_size = _get_struct_size (p);
449
450   memcpy (stats, &s, n);
451 }
452
453 static void
454 open_trace_file ()
455 {
456   char msg1[] =
457     "\n"
458     "Pool allocator running in trace mode.\n"
459     "Trace is being saved to file ";
460   char msg2[] = "\n\n";
461
462   trace_filename = getenv ("POOL_TRACE");
463
464   if (trace_filename)
465     {
466       trace_fd = open (trace_filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
467       if (trace_fd == -1)
468         {
469           perror (trace_filename);
470           exit (1);
471         }
472
473       write (2, msg1, sizeof msg1);
474       write (2, trace_filename, strlen (trace_filename));
475       write (2, msg2, sizeof msg2);
476     }
477 }
478
479 static void
480 trace (const char *fn, void *caller, struct pool *ptr1, void *ptr2, void *ptr3, int i1)
481 {
482   char buffer[128];
483
484   sprintf (buffer,
485            "%s caller: %p ptr1: %p ptr2: %p ptr3: %p i1: %d\n",
486            fn, caller, ptr1, ptr2, ptr3, i1);
487   write (trace_fd, buffer, strlen (buffer));
488 }