Inherit lock from parent when creating SilcStack child.
[silc.git] / lib / silcutil / silcstack.c
1 /*
2
3   silcstack.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2003 - 2007 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21
22 /************************** Types and definitions ***************************/
23
24 /* The SilcStack context */
25 struct SilcStackStruct {
26   SilcStack parent;                           /* Parent stack */
27   SilcMutex lock;                             /* Stack lock */
28   SilcList stacks;                            /* List of stacks for childs */
29   SilcStackDataEntry stack;                   /* The allocated stack */
30   SilcStackFrame *frames;                     /* Allocated stack frames */
31   SilcStackFrame *frame;                      /* Current stack frame */
32   SilcStackOomHandler oom_handler;            /* OOM handler */
33   void *oom_context;                          /* OOM handler context */
34   SilcUInt32 stack_size;                      /* Default stack size */
35   SilcUInt32 alignment;                       /* Memory alignment */
36 #ifdef SILC_DIST_INPLACE
37   /* Statistics */
38   SilcUInt32 snum_malloc;
39   SilcUInt32 sbytes_malloc;
40   SilcUInt32 snum_errors;
41 #endif /* SILC_DIST_INPLACE */
42 };
43
44 /************************ Static utility functions **************************/
45
46 /* Compute stack block index for the `size'. */
47
48 static SilcUInt32 silc_stack_get_index(SilcUInt32 size, SilcUInt32 *ret_bsize)
49 {
50   SilcUInt32 bsize, si;
51
52   if (size < SILC_STACK_DEFAULT_SIZE)
53     size = SILC_STACK_DEFAULT_SIZE;
54   si = 0;
55   bsize = SILC_STACK_DEFAULT_SIZE;
56   while (bsize < size) {
57     bsize <<= 1;
58     si++;
59   }
60
61   *ret_bsize = bsize;
62
63   return si;
64 }
65
66 /* Get stack from `stack' or allocate new one. */
67
68 static SilcStackDataEntry silc_stack_ref_stack(SilcStack stack,
69                                                SilcUInt32 size,
70                                                SilcUInt32 *ret_si,
71                                                SilcUInt32 *ret_bsize)
72 {
73   SilcStackDataEntry e;
74   SilcUInt32 si, bsize;
75
76   /* Get stack block index and block size for requested size */
77   si = silc_stack_get_index(size, &bsize);
78   *ret_si = si;
79   *ret_bsize = bsize;
80
81   SILC_ST_DEBUG(("Get stack block, si %d, size %lu, stack %p",
82                  si, bsize, stack));
83
84   silc_mutex_lock(stack->lock);
85
86   /* Get stack that has block that can house our size requirement. */
87   silc_list_start(stack->stacks);
88   while ((e = silc_list_get(stack->stacks))) {
89     if (!e->data[si])
90       continue;
91
92     silc_list_del(stack->stacks, e);
93     SILC_ST_DEBUG(("Got stack blocks %p from stack %p", e->data, stack));
94     silc_mutex_unlock(stack->lock);
95     return e;
96   }
97
98   /* If we are child, get block from parent */
99   if (stack->parent) {
100     silc_mutex_unlock(stack->lock);
101     return silc_stack_ref_stack(stack->parent, size, ret_si, ret_bsize);
102   }
103
104   SILC_ST_DEBUG(("Allocate new stack blocks"));
105
106   /* Allocate new stack blocks */
107   e = silc_calloc(1, sizeof(*e));
108   if (!e) {
109     silc_mutex_unlock(stack->lock);
110     return NULL;
111   }
112   e->data[si] = silc_malloc(bsize + SILC_STACK_ALIGN(sizeof(*e->data[0]),
113                                                      stack->alignment));
114   if (!e->data[si]) {
115     silc_free(e);
116     silc_mutex_unlock(stack->lock);
117     return NULL;
118   }
119   e->data[si]->bytes_left = bsize;
120   e->si = si;
121   e->bsize = bsize;
122
123   SILC_ST_DEBUG(("Got stack blocks %p from stack %p", e->data, stack));
124
125   silc_mutex_unlock(stack->lock);
126   return e;
127 }
128
129 /* Return the `data' back to the `stack'. */
130
131 static void silc_stack_unref_stack(SilcStack stack, SilcStackDataEntry e)
132 {
133   int i;
134
135   SILC_LOG_DEBUG(("Release stack blocks %p to stack %p, si %d",
136                   e->data, stack, e->si));
137
138   /* Release all blocks from allocations */
139   for (i = e->si; i < SILC_STACK_BLOCK_NUM; i++) {
140     if (!e->data[i])
141       continue;
142     if (!i)
143       e->data[i]->bytes_left = e->bsize;
144     else
145       e->data[i]->bytes_left = SILC_STACK_BLOCK_SIZE(stack, i);
146   }
147
148   silc_mutex_lock(stack->lock);
149   silc_list_add(stack->stacks, e);
150   silc_mutex_unlock(stack->lock);
151 }
152
153 /* Allocate memory from a specific stack block */
154
155 static void *silc_stack_alloc_block(SilcStack stack, SilcStackDataEntry e,
156                                     SilcUInt32 items, SilcUInt32 size)
157 {
158   SilcUInt32 asize;
159   void *ptr;
160
161   /* Get pointer and consume the stack block */
162   asize = SILC_STACK_ALIGN(items * size, stack->alignment);
163   ptr = SILC_STACK_DATA_EXT(e->data, e->si, e->bsize, stack->alignment);
164   e->data[e->si]->bytes_left -= asize;
165   memset(ptr, 0, items * size);
166
167   return ptr;
168 }
169
170 /***************************** SilcStack API ********************************/
171
172 /* Allocate the stack */
173
174 SilcStack silc_stack_alloc(SilcUInt32 stack_size, SilcStack parent)
175 {
176   SilcStack stack;
177   SilcStackDataEntry e;
178   SilcUInt32 si = 0, bsize = 0;
179
180   stack_size = stack_size ? stack_size : SILC_STACK_DEFAULT_SIZE;
181   if (stack_size < SILC_STACK_DEFAULT_SIZE)
182     stack_size = SILC_STACK_DEFAULT_SIZE;
183
184   if (parent) {
185     /* Get stack from parent.  The stack itself is allocated from the
186        parent. */
187     e = silc_stack_ref_stack(parent, stack_size, &si, &bsize);
188     if (!e)
189       return NULL;
190
191     /* Allocate stack from the parent */
192     stack = silc_stack_alloc_block(parent, e, 1, sizeof(*stack));
193     if (!stack) {
194       silc_stack_unref_stack(parent, e);
195       return NULL;
196     }
197
198     stack->parent = parent;
199     stack->stack_size = stack_size;
200     stack->alignment = SILC_STACK_DEFAULT_ALIGN;
201     stack->oom_handler = parent->oom_handler;
202     stack->oom_context = parent->oom_context;
203     stack->lock = parent->lock;
204     silc_list_init(stack->stacks, struct SilcStackDataEntryStruct, next);
205
206     /* Allocate stack frames from the parent */
207     stack->frames = silc_stack_alloc_block(parent, e, SILC_STACK_BLOCK_NUM,
208                                            sizeof(*stack->frames));
209     if (!stack->frames) {
210       silc_stack_unref_stack(parent, e);
211       return NULL;
212     }
213
214     /* Set the initial stack */
215     stack->stack = e;
216   } else {
217     /* Dynamically allocate new stack */
218     stack = silc_calloc(1, sizeof(*stack));
219     if (!stack)
220       return NULL;
221
222     stack->stack_size = stack_size;
223     stack->alignment = SILC_STACK_DEFAULT_ALIGN;
224     silc_list_init(stack->stacks, struct SilcStackDataEntryStruct, next);
225
226     /* Create initial stack */
227     stack->stack = silc_calloc(1, sizeof(*stack->stack));
228     if (!stack->stack) {
229       silc_free(stack);
230       return NULL;
231     }
232     stack->stack->data[0] =
233       silc_malloc(stack->stack_size +
234                   SILC_STACK_ALIGN(sizeof(*stack->stack->data[0]),
235                                    stack->alignment));
236     if (!stack->stack->data[0]) {
237       silc_free(stack->stack);
238       silc_free(stack);
239       return NULL;
240     }
241     stack->stack->data[0]->bytes_left = stack->stack_size;
242     stack->stack->si = 0;
243     stack->stack->bsize = stack->stack_size;
244
245     /* Allocate stack frames from the stack itself */
246     stack->frames = silc_stack_alloc_block(stack, stack->stack,
247                                            SILC_STACK_DEFAULT_NUM,
248                                            sizeof(*stack->frames));
249     if (!stack->frames) {
250       silc_free(stack->stack->data[0]);
251       silc_free(stack->stack);
252       silc_free(stack);
253       return NULL;
254     }
255
256     /* Allocate lock */
257     silc_mutex_alloc(&stack->lock);
258   }
259
260   /* Use the allocated stack in first stack frame */
261   stack->frame = &stack->frames[0];
262   stack->frame->prev = NULL;
263   stack->frame->bytes_used = stack->stack_size;
264   stack->frame->sp = 1;
265   stack->frame->si = si;
266
267   SILC_LOG_DEBUG(("New stack %p, size %d bytes", stack, stack->stack_size));
268
269   return stack;
270 }
271
272 /* Frees the stack and all allocated memory */
273
274 void silc_stack_free(SilcStack stack)
275 {
276   SilcStackDataEntry e;
277   int i;
278
279   if (!stack)
280     return;
281
282   SILC_LOG_DEBUG(("Free stack %p", stack));
283
284   if (!stack->parent) {
285     silc_list_start(stack->stacks);
286     while ((e = silc_list_get(stack->stacks))) {
287       for (i = 0; i < SILC_STACK_BLOCK_NUM; i++)
288         silc_free(e->data[i]);
289       silc_free(e);
290     }
291
292     for (i = 0; i < SILC_STACK_BLOCK_NUM; i++)
293       silc_free(stack->stack->data[i]);
294     silc_free(stack->stack);
295
296     if (stack->lock)
297       silc_mutex_free(stack->lock);
298
299     silc_free(stack);
300   } else {
301     /* Return all stack blocks to the parent */
302     silc_list_start(stack->stacks);
303     while ((e = silc_list_get(stack->stacks)))
304       silc_stack_unref_stack(stack->parent, e);
305
306     silc_stack_unref_stack(stack->parent, stack->stack);
307   }
308 }
309
310 /* Push to next stack frame */
311
312 SilcUInt32 silc_stack_push(SilcStack stack, SilcStackFrame *frame)
313 {
314   if (!stack)
315     return 0;
316
317   if (!frame) {
318     if (stack->frame->sp >= SILC_STACK_ALIGN(stack->frame->sp,
319                                              SILC_STACK_DEFAULT_NUM)) {
320       SILC_LOG_DEBUG(("SilcStack %p running out of frames, cannot push",
321                       stack));
322       return stack->frame->sp;
323     }
324
325     frame = &stack->frames[stack->frame->sp];
326   }
327
328   /* Push */
329   frame->prev = stack->frame;
330   frame->sp = stack->frame->sp + 1;
331   frame->si = stack->frame->si;
332   frame->bytes_used = stack->stack->data[frame->si]->bytes_left;
333   stack->frame = frame;
334
335   SILC_ST_DEBUG(("Push %p: sp %d -> %d, si %d", stack, frame->prev->sp,
336                  frame->sp, frame->si));
337
338   return stack->frame->sp;
339 }
340
341 /* Pop to previous stack frame */
342
343 SilcUInt32 silc_stack_pop(SilcStack stack)
344 {
345   SilcUInt32 si;
346
347   if (!stack || !stack->frame->prev)
348     return 0;
349
350   /* Pop */
351   si = stack->frame->si;
352   while (si > stack->frame->prev->si) {
353     if (stack->stack->data[si])
354       stack->stack->data[si]->bytes_left = SILC_STACK_BLOCK_SIZE(stack, si);
355     si--;
356   }
357   stack->stack->data[si]->bytes_left = stack->frame->bytes_used;
358   stack->frame = stack->frame->prev;
359
360   SILC_ST_DEBUG(("Pop %p: sp %d -> %d, si %d", stack, stack->frame->sp + 1,
361                  stack->frame->sp, stack->frame->si));
362
363   return stack->frame->sp + 1;
364 }
365
366 /* Allocate memory.  Returns pointer to the memory or NULL on error. */
367
368 void *silc_stack_malloc(SilcStack stack, SilcUInt32 size)
369 {
370   void *ptr;
371   SilcUInt32 bsize, bsize2;
372   SilcUInt32 si = stack->frame->si;
373
374   SILC_STACK_STAT(stack, num_malloc, 1);
375   SILC_ST_DEBUG(("Allocating %d bytes from %p", size, stack));
376
377   if (silc_unlikely(!size)) {
378     SILC_LOG_ERROR(("Allocation by zero (0)"));
379     SILC_STACK_STAT(stack, num_errors, 1);
380     return NULL;
381   }
382
383   if (silc_unlikely(size > SILC_STACK_MAX_ALLOC)) {
384     SILC_LOG_ERROR(("Allocating too much"));
385     SILC_STACK_STAT(stack, num_errors, 1);
386     if (stack->oom_handler)
387       stack->oom_handler(stack, stack->oom_context);
388     return NULL;
389   }
390
391   /* Align properly  */
392   size = SILC_STACK_ALIGN(size, stack->alignment);
393
394   /* Compute the size of current stack block */
395   bsize = SILC_STACK_BLOCK_SIZE(stack, si);
396
397   /* See if there is space in the current stack block */
398   if (stack->stack->data[si]->bytes_left >= size) {
399     /* Get pointer to the memory */
400     ptr = SILC_STACK_DATA(stack, si, bsize);
401     stack->stack->data[si]->bytes_left -= size;
402     SILC_STACK_STAT(stack, bytes_malloc, size);
403     return ptr;
404   }
405
406   /* There is not enough space in this block.  Find the spot to stack
407      block that can handle this size memory. */
408   if (bsize < SILC_STACK_DEFAULT_SIZE)
409     bsize = SILC_STACK_DEFAULT_SIZE;
410   bsize += size;
411   bsize2 = SILC_STACK_DEFAULT_SIZE;
412   si = 0;
413   while (bsize2 < bsize) {
414     bsize2 <<= 1;
415     si++;
416   }
417   if (silc_unlikely(si >= SILC_STACK_BLOCK_NUM)) {
418     SILC_LOG_ERROR(("Allocating too large block"));
419     SILC_STACK_STAT(stack, num_errors, 1);
420     if (stack->oom_handler)
421       stack->oom_handler(stack, stack->oom_context);
422     return NULL;
423   }
424
425   /* Allocate the block if it doesn't exist yet */
426   if (!stack->stack->data[si]) {
427     SILC_ST_DEBUG(("Allocating new stack block, %d bytes", bsize2));
428     stack->stack->data[si] =
429       silc_malloc(bsize2 +
430                   SILC_STACK_ALIGN(sizeof(**stack->stack->data),
431                                    stack->alignment));
432     if (silc_unlikely(!stack->stack->data[si])) {
433       SILC_STACK_STAT(stack, num_errors, 1);
434       if (stack->oom_handler)
435         stack->oom_handler(stack, stack->oom_context);
436       return NULL;
437     }
438     stack->stack->data[si]->bytes_left = bsize2;
439   }
440
441   /* Now return memory from this new block.  It is guaranteed that in this
442      block there is enough space for this memory. */
443   assert(stack->stack->data[si]->bytes_left >= size);
444   ptr = SILC_STACK_DATA(stack, si, bsize2);
445   stack->stack->data[si]->bytes_left -= size;
446   stack->frame->si = si;
447   SILC_STACK_STAT(stack, bytes_malloc, size);
448
449   return ptr;
450 }
451
452 /* Attempts to reallocate memory by changing the size of the `ptr' into
453    `size'.  This routine works only if the previous allocation to `stack'
454    was `ptr'.  If there is another memory allocation between allocating
455    `ptr' and this call this routine will return NULL.  NULL is also returned
456    if the `size' does not fit into the current block.  If NULL is returned
457    the old memory remains intact. */
458
459 void *silc_stack_realloc(SilcStack stack, SilcUInt32 old_size,
460                          void *ptr, SilcUInt32 size)
461 {
462   SilcUInt32 si = stack->frame->si;
463   SilcUInt32 bsize;
464   void *sptr;
465
466   if (!ptr)
467     return silc_stack_malloc(stack, size);
468
469   SILC_STACK_STAT(stack, num_malloc, 1);
470   SILC_ST_DEBUG(("Reallocating %d bytes (%d) from %p", size, old_size, stack));
471
472   if (silc_unlikely(!size || !old_size)) {
473     SILC_LOG_ERROR(("Allocation by zero (0)"));
474     SILC_STACK_STAT(stack, num_errors, 1);
475     return NULL;
476   }
477
478   if (silc_unlikely(size > SILC_STACK_MAX_ALLOC)) {
479     SILC_LOG_ERROR(("Allocating too much"));
480     SILC_STACK_STAT(stack, num_errors, 1);
481     if (stack->oom_handler)
482       stack->oom_handler(stack, stack->oom_context);
483     return NULL;
484   }
485
486   /* Align properly */
487   old_size = SILC_STACK_ALIGN(old_size, stack->alignment);
488
489   /* Compute the size of current stack block */
490   bsize = SILC_STACK_BLOCK_SIZE(stack, si);
491
492   /* Check that `ptr' is last allocation */
493   sptr = (unsigned char *)stack->stack->data[si] +
494     SILC_STACK_ALIGN(sizeof(**stack->stack->data), stack->alignment);
495   if (stack->stack->data[si]->bytes_left + old_size +
496       ((unsigned char *)ptr - (unsigned char *)sptr) != bsize) {
497     SILC_LOG_DEBUG(("Cannot reallocate"));
498     SILC_STACK_STAT(stack, num_errors, 1);
499     return NULL;
500   }
501
502   /* Now check that the new size fits to this block */
503   if (stack->stack->data[si]->bytes_left >= size) {
504     /* It fits, so simply return the old pointer */
505     size = SILC_STACK_ALIGN(size, stack->alignment);
506     stack->stack->data[si]->bytes_left -= (size - old_size);
507     SILC_STACK_STAT(stack, bytes_malloc, (size - old_size));
508     return ptr;
509   }
510
511   SILC_LOG_DEBUG(("Cannot reallocate in this block"));
512   SILC_STACK_STAT(stack, num_errors, 1);
513   return NULL;
514 }
515
516 /* Set OOM handler */
517
518 void silc_stack_set_oom_handler(SilcStack stack,
519                                 SilcStackOomHandler oom_handler,
520                                 void *context)
521 {
522   stack->oom_handler = oom_handler;
523   stack->oom_context = context;
524 }
525
526 /* Set default alignment */
527
528 void silc_stack_set_alignment(SilcStack stack, SilcUInt32 alignment)
529 {
530   SILC_LOG_DEBUG(("Set stack %p alignment to %d bytes", stack, alignment));
531   stack->alignment = alignment;
532 }
533
534 /* Get default alignment */
535
536 SilcUInt32 silc_stack_get_alignment(SilcStack stack)
537 {
538   return stack->alignment;
539 }
540
541 /* Purge stack */
542
543 SilcBool silc_stack_purge(SilcStack stack)
544 {
545   SilcStackDataEntry e;
546   SilcBool ret = FALSE;
547   int i;
548
549   SILC_LOG_DEBUG(("Purge stack %p", stack));
550
551   /* Go through the default stack */
552   for (i = SILC_STACK_BLOCK_NUM - 1; i > 3; i--) {
553     if (stack->stack->data[i] &&
554         stack->stack->data[i]->bytes_left == SILC_STACK_BLOCK_SIZE(stack, i)) {
555       SILC_LOG_DEBUG(("Purge %d bytes",
556                       SILC_STACK_BLOCK_SIZE(stack, i)));
557       silc_free(stack->stack->data[i]);
558       stack->stack->data[i] = NULL;
559       ret = TRUE;
560     }
561   }
562
563   silc_mutex_lock(stack->lock);
564
565   /* Remove one child stack */
566   if (silc_list_count(stack->stacks) > 2) {
567     silc_list_start(stack->stacks);
568     e = silc_list_get(stack->stacks);
569
570     SILC_LOG_DEBUG(("Remove stack blocks %p", e->data));
571     silc_list_del(stack->stacks, e);
572     ret = TRUE;
573
574     for (i = 0; i < SILC_STACK_BLOCK_NUM; i++)
575       silc_free(e->data[i]);
576     silc_free(e);
577   }
578
579   /* Go through the child stacks */
580   silc_list_start(stack->stacks);
581   while ((e = silc_list_get(stack->stacks))) {
582     for (i = SILC_STACK_BLOCK_NUM - 1; i > 3; i--) {
583       if (e->data[i]) {
584         SILC_LOG_DEBUG(("Purge %d bytes",
585                         SILC_STACK_BLOCK_SIZE(stack, i)));
586         silc_free(e->data[i]);
587         e->data[i] = NULL;
588         ret = TRUE;
589       }
590     }
591   }
592
593   silc_mutex_unlock(stack->lock);
594
595   return ret;
596 }
597
598 #ifdef SILC_DIST_INPLACE
599 /* Statistics dumping. */
600
601 void silc_stack_stats(SilcStack stack)
602 {
603   SilcStackDataEntry e;
604   SilcUInt32 stack_size = 0;
605   int i, c = 0;
606
607   for (i = 0; i < SILC_STACK_BLOCK_NUM; i++) {
608     if (!stack->stack->data[i])
609       continue;
610     stack_size += SILC_STACK_BLOCK_SIZE(stack, i);
611     c++;
612   }
613
614   fprintf(stdout, "\nSilcStack %p statistics :\n\n", stack);
615   fprintf(stdout, "  Size of stack           : %u\n",
616           (unsigned int)stack_size);
617   fprintf(stdout, "  Stack alignment         : %d\n",
618           (int)stack->alignment);
619   fprintf(stdout, "  Number of allocs        : %u\n",
620           (unsigned int)stack->snum_malloc);
621   fprintf(stdout, "  Bytes allocated         : %u\n",
622           (unsigned int)stack->sbytes_malloc);
623   fprintf(stdout, "  Average alloc size      : %.2f\n",
624           (double)((double)stack->sbytes_malloc / (double)stack->snum_malloc));
625   fprintf(stdout, "  Number of alloc errors  : %u\n",
626           (unsigned int)stack->snum_errors);
627   fprintf(stdout, "  Number of frames        : %u\n",
628           (unsigned int)SILC_STACK_ALIGN(stack->frame->sp,
629                                          SILC_STACK_DEFAULT_NUM));
630   fprintf(stdout, "  Number of blocks        : %u\n", c);
631   fprintf(stdout, "  Number of stacks        : %d\n",
632           silc_list_count(stack->stacks));
633
634   silc_list_start(stack->stacks);
635   while ((e = silc_list_get(stack->stacks))) {
636     stack_size = 0;
637     c = 0;
638     for (i = 0; i < SILC_STACK_BLOCK_NUM; i++) {
639       if (!e->data[i])
640         continue;
641       stack_size += e->data[i]->bytes_left;
642       c++;
643     }
644     fprintf(stdout, "\n  Size of stack           : %u\n",
645             (unsigned int)stack_size);
646     fprintf(stdout, "  Number of blocks        : %u\n", c);
647   }
648 }
649 #endif /* SILC_DIST_INPLACE */