1 /* tag: Tom Lord Tue Dec  4 14:41:27 2001 (alloc-limits.c)
2  */
3 /* alloc-limits.c -
4  *
5  ****************************************************************
6  * Copyright (C) 2000 Tom Lord
7  *
8  * See the file "COPYING" for further information about
9  * the copyright and warranty status of this work.
10  */
11 
12 
13 #include "hackerlab/machine/alignment.h"
14 #include "hackerlab/os/malloc.h"
15 #include "hackerlab/os/stdarg.h"
16 #include "hackerlab/bugs/panic.h"
17 #include "hackerlab/mem/mem.h"
18 #include "hackerlab/mem/must-malloc.h"
19 #include "hackerlab/mem/alloc-limits.h"
20 
21 
22 /************************************************************************
23  *(h1 "Allocation With Limitations"
24  *  	:includes ("hackerlab/mem/alloc-limits.h"))
25  *
26  * |allocation limits|
27  * |limited allocation|
28  *
29  * In some situations it is desriable for a subsystem of a program
30  * to limit its use of memory, independently of the rest of the program.
31  * The `alloc_limits' functions are for such situations.
32  *
33  * There are two steps to using the `alloc_limits' functions.  First,
34  * you create an `alloc_limits' object which specifies how much memory
35  * your subsystem should, ideally, use, and how much it is permitted to use.
36  * See xref:"make_alloc_limits".  Second, use functions like `lim_malloc'
37  * and `lim_free' instead of functions like `malloc' and `free' in the
38  * subsystem to which the limits apply.
39  *
40  *
41  */
42 /*(menu)
43  */
44 
45 
46 
47 #define SIZEOF_HEADER		((sizeof (size_t) > MACHINE_ALIGNMENT) ? sizeof (size_t) : MACHINE_ALIGNMENT)
48 #define MEM_TO_HEADER(B)	((size_t *)((char *)(B) - SIZEOF_HEADER))
49 #define HEADER_TO_MEM(H)	((void *)((char *)(H) + SIZEOF_HEADER))
50 
51 
52 struct alloc_limits
53 {
54   t_uchar * name;
55 
56   size_t threshold;		/* where does allocation trigger gc? */
57   size_t failure_pt;		/* where does allocation fail? */
58 
59   /* threshold == 0		never trigger gc
60    * failure_pt == 0		never fail
61    */
62 
63   int panic_on_failure;
64 
65   size_t in_use;
66   size_t high_water_mark;
67 
68   lim_free_memory_fn free_memory;
69   void * closure;
70 };
71 
72 struct alloc_limits lim_use_malloc_limits =
73 {
74   "alloc_limits for generic malloc",
75   0, 0, 0, 0, 0, 0, 0
76 };
77 
78 struct alloc_limits lim_no_allocations_limits =
79 {
80   "alloc_limits which prohibit allocation",
81   0, 1, 0, 1, 1, 0, 0
82 };
83 
84 
85 /************************************************************************
86  *(h2 "Specifying Allocation Limits")
87  *
88  * An object of the opaque type `alloc_limits' records the rules which
89  * limit memory use in a particular subsystem.
90  *
91  */
92 
93 
94 /*(c make_alloc_limits)
95  * alloc_limits make_alloc_limits (t_uchar * name,
96  *                                 size_t threshold,
97  *                                 size_t failure_pt,
98  *                                 int panic_on_failure,
99  *                                 lim_free_memory_fn free_memory,
100  *                                 void * closure);
101  *
102  * Create a new `alloc_limits' object.
103  *
104  * `name' is the name of the subsystem to which these allocation limits
105  * apply.  The name is useful for debugging purposes.
106  *
107  * `threshold' specifies an ideal limit on the amount of memory used
108  * by your subsystem.  If a subsystem attempts to allocate more than
109  * `threshold' bytes, the `free_memory' function for that subsystem
110  * is invoked (see below).  `threshold' may be 0, in which case the
111  * `free_memory' function is never invoked.
112  *
113  * `failure_pt' specifies an absolute limit on the amount of memory
114  * used by your subsystem.  Allocations beyond `failure_pt' fail
115  * (return 0).
116  *
117  * `panic_on_failure', if non-0, means that if the failure point is
118  * reached, or allocation fails, the program will exit with a
119  * message to the standard error stream and a non-0 status.
120  * If 0, allocation failures return 0.
121  *
122  * |$lim_free_memory_fn|
123  *
124  * `free_memory' is a function pointer:
125  *
126  * 	typedef void (*lim_free_memory_fn)(void * closure,
127  * 					   size_t needed);
128  *
129  * It is called immediately before an allocation that would exceed
130  * `threshold' (if `threshold' is not 0).  `closure' is as passed
131  * to `make_alloc_limits' (see below).  `needed' is the number of
132  * bytes by which the proposed allocation causes the total amount of
133  * memory used by the subsystem to exceed `threshold'.  It is completely
134  * safe to call `lim_free' from your `free_memory' function.  It
135  * is possible to call `lim_malloc' or `lim_realloc', but if your
136  * program does this, `free_memory' must be reentrant.
137  *
138  * `closure' is an opaque value passed to `free_memory'.
139  *
140  * If a new `alloc_limits' object can not be allocated, this function
141  * calls `panic' and does not return.
142  *
143  * As an alternative to calling `make_alloc_limits' in some situations,
144  * three pre-defined allocation limits are declared in `alloc-limits.h':
145  *
146  *	extern alloc_limits lim_use_malloc;
147  *	extern alloc_limits lim_use_must_malloc;
148  *	extern alloc_limits lim_no_allocations;
149  *
150  * Allocations performed with `lim_use_malloc' are unlimited and are
151  * effectively like allocations performed with `malloc' or `realloc'.
152  * Failed allocations return 0.
153  *
154  * Allocations performed with `lim_use_must_malloc' are unlimited and
155  * are effectively like allocations performed with `must_malloc' or
156  * `must_realloc'.  Failed allocations cause the process to exit with
157  * a non-0 status.
158  *
159  * Allocations performed with `lim_no_allocations' always fail and return
160  * 0.  `lim_free' has no effect when passed `lim_no_allocations'.
161  */
162 alloc_limits
make_alloc_limits(t_uchar * name,size_t threshold,size_t failure_pt,int panic_on_failure,lim_free_memory_fn free_memory,void * closure)163 make_alloc_limits (t_uchar * name,
164 		   size_t threshold,
165 		   size_t failure_pt,
166 		   int panic_on_failure,
167 		   lim_free_memory_fn free_memory,
168 		   void * closure)
169 {
170   alloc_limits answer;
171 
172   answer = (alloc_limits)must_malloc (sizeof (struct alloc_limits));
173 
174   answer->name = name;
175   answer->threshold = threshold;
176   answer->failure_pt = failure_pt;
177   answer->panic_on_failure = panic_on_failure;
178   answer->in_use = 0;
179   answer->high_water_mark = 0;
180   answer->free_memory = free_memory;
181   answer->closure = closure;
182 
183   return answer;
184 }
185 
186 /*(c free_alloc_limits)
187  * void free_alloc_limits (alloc_limits limits);
188  *
189  * Free an allocation limits object.
190  */
191 void
free_alloc_limits(alloc_limits limits)192 free_alloc_limits (alloc_limits limits)
193 {
194   must_free ((void *)limits);
195 }
196 
197 
198 
199 /*(c lim_set_threshold)
200  * size_t lim_set_threshold (alloc_limits it, size_t threshold);
201  *
202  * Modify the `threshold' of an `alloc_limits' object.  Return
203  * the old `threshold'.
204  *
205  * This function does not immediately call `free_memory', even if
206  * the total amount of memory allocated exceeds the new `threshold'.
207  */
208 size_t
lim_set_threshold(alloc_limits it,size_t threshold)209 lim_set_threshold (alloc_limits it, size_t threshold)
210 {
211   size_t old;
212   old = it->threshold;
213   it->threshold = threshold;
214   return old;
215 }
216 
217 
218 /*(c lim_threshold)
219  * size_t lim_threshold (alloc_limits limits);
220  *
221  * Return the current `threshold' of allocation limits `limits'.
222  */
223 size_t
lim_threshold(alloc_limits limits)224 lim_threshold (alloc_limits limits)
225 {
226   return limits->threshold;
227 }
228 
229 
230 /*(c lim_set_failure_pt)
231  * size_t lim_set_failure_pt (alloc_limits limits, size_t failure_pt);
232  *
233  * Modify the `failure_pt' of an `alloc_limits' object.  Return
234  * the old `failure_pt'.
235  *
236  */
237 size_t
lim_set_failure_pt(alloc_limits limits,size_t failure_pt)238 lim_set_failure_pt (alloc_limits limits, size_t failure_pt)
239 {
240   size_t old;
241   old = limits->failure_pt;
242   limits->failure_pt = failure_pt;
243   return old;
244 }
245 
246 
247 /*(c lim_failure_pt)
248  * size_t lim_failure_pt (alloc_limits limits);
249  *
250  * Return the `failure_pt' of allocation limits `limits'.
251  */
252 size_t
lim_failure_pt(alloc_limits limits)253 lim_failure_pt (alloc_limits limits)
254 {
255   return limits->failure_pt;
256 }
257 
258 
259 /*(c lim_is_panic_on_failure)
260  * int lim_is_panic_on_failure (alloc_limits limits);
261  *
262  * Return the value of the `panic_on_failure' flag of
263  * allocation limits `limits'.
264  */
265 int
lim_is_panic_on_failure(alloc_limits limits)266 lim_is_panic_on_failure (alloc_limits limits)
267 {
268   return limits->panic_on_failure;
269 }
270 
271 
272 /*(c lim_set_panic_on_failure)
273  * int lim_set_panic_on_failure (alloc_limits limits, int value);
274  *
275  * Set the `panic_on_failure' flag of allocation limits `limits'.
276  *
277  * Return the old value.
278  */
279 int
lim_set_panic_on_failure(alloc_limits limits,int value)280 lim_set_panic_on_failure (alloc_limits limits, int value)
281 {
282   int was;
283 
284   was = limits->panic_on_failure;
285   limits->panic_on_failure = value;
286   return was;
287 }
288 
289 /*(c lim_in_use)
290  * size_t lim_in_use (alloc_limits limits);
291  *
292  * Return the amount (in bytes) of memory allocation charged
293  * to `limits' and not yet freed.
294  */
295 size_t
lim_in_use(alloc_limits limits)296 lim_in_use (alloc_limits limits)
297 {
298   return limits->in_use;
299 }
300 
301 
302 /*(c lim_high_water_mark)
303  * size_t lim_high_water_mark (alloc_limits limits);
304  *
305  * Return the largest amount (in bytes) of outstanding memory
306  * allocation charged to `limits' during the lifetime of the process.
307  */
308 size_t
lim_high_water_mark(alloc_limits limits)309 lim_high_water_mark (alloc_limits limits)
310 {
311   return limits->high_water_mark;
312 }
313 
314 
315 
316 
317 /************************************************************************
318  *(h2 "Allocating and Freeing")
319  *
320  * These functions allocate and free memory, similarly to
321  * `malloc', `realloc', and `free', but with allocation limits.
322  *
323  */
324 
325 
326 /*(c lim_malloc)
327  * void * lim_malloc (alloc_limits limits, size_t amt);
328  *
329  * Allocate `amt' bytes of memory.
330  *
331  * If the allocation would exceed the threshold of `limits',
332  * invoke the `free_memory' function first.
333  *
334  * If the allocation would exceed the `failure_pt' of `limits', or if
335  * the underlying `malloc' fails, return 0 (if the `panic_on_failure'
336  * flag of `limits' is 0) or exit the process with a non-0 status (if
337  * `panic_on_failure' is non-0).
338  *
339  * Adjust the `in_use' and `high_water_mark' of `limits'.
340  *
341  * If `limits' is 0, this function works like xref:"must_malloc".
342  *
343  */
344 void *
lim_malloc(alloc_limits limits,size_t amt)345 lim_malloc (alloc_limits limits, size_t amt)
346 {
347   if (!limits)
348     return must_malloc (amt);
349 
350   if (lim_prepare (limits, amt))
351     {
352       if (limits->panic_on_failure)
353 	{
354 	  panic_msg ("allocation failure");
355 	  panic (limits->name);
356 	}
357       else
358 	return 0;
359     }
360 
361   return lim_soft_malloc (limits, amt);
362 }
363 
364 
365 /*(c lim_zalloc)
366  * void * lim_zalloc (alloc_limits limits, size_t amt);
367  *
368  * Use `lim_malloc' to attempt to allocate `amt' bytes
369  * of memory.   If allocation succeeds, initialize that
370  * memory to all zero bytes.
371  *
372  * Return the allocated region or 0 if allocation fails.
373  */
374 void *
lim_zalloc(alloc_limits limits,size_t amt)375 lim_zalloc (alloc_limits limits, size_t amt)
376 {
377   void * answer;
378 
379   answer = lim_malloc (limits, amt);
380   if (answer)
381     mem_set0 ((t_uchar *)answer, amt);
382 
383   return answer;
384 }
385 
386 
387 /*(c lim_soft_malloc)
388  * void * lim_soft_malloc (alloc_limits limits, size_t amt);
389  *
390  * Allocate `amt' bytes of memory.  Return the newly allocated memory.
391  *
392  * If the allocation would exceed the `failure_pt' of `limits',
393  * or if the underlying `malloc' fails, return 0 (if the
394  * `panic_on_failure' flag of `limits' is 0) or exit the
395  * process with a non-0 status.
396  *
397  * Adjust the `in_use' and `high_water_mark' of `limits'.
398  *
399  * This function *does not* invoke the `free_memory' function of
400  * `limits', even if the allocation would exceed the threshold of
401  * `limits'.
402  *
403  * If `limits' is 0, this function works like xref:"must_malloc".
404  *
405  */
406 void *
lim_soft_malloc(alloc_limits limits,size_t amt)407 lim_soft_malloc (alloc_limits limits, size_t amt)
408 {
409   void * answer;
410 
411   if (!limits)
412     return must_malloc (amt);
413 
414   if (limits->failure_pt && (limits->in_use + amt >= limits->failure_pt))
415     {
416       if (limits->panic_on_failure)
417 	{
418 	  panic_msg ("allocation failure");
419 	  panic (limits->name);
420 	}
421       else
422 	return 0;
423     }
424 
425   answer = (void *)malloc (amt + SIZEOF_HEADER);
426 
427   if (!answer)
428     {
429       if (limits->panic_on_failure)
430 	{
431 	  panic_msg ("allocation failure");
432 	  panic (limits->name);
433 	}
434       else
435 	return 0;
436     }
437 
438   *(size_t *)answer = amt;
439   limits->in_use += amt;
440   if (limits->in_use > limits->high_water_mark)
441     limits->high_water_mark = limits->in_use;
442   return HEADER_TO_MEM (answer);
443 }
444 
445 
446 /*(c lim_realloc)
447  * void * lim_realloc (alloc_limits limits, void * prev, size_t amt);
448  *
449  * Reallocate `prev' as `amt' bytes of memory.  Copy (up to) `amt' bytes
450  * of data from `prev' to the newly allocated memory.
451  *
452  * If the allocation would exceed the threshold of `limits',
453  * invoke the `free_memory' function first.
454  *
455  * If the allocation would exceed the `failure_pt' of `limits',
456  * or if the underlying `malloc' fails, return 0 (if the
457  * `panic_on_failure' flag of `limits' is 0) or exit the
458  * process with a non-0 status.
459  *
460  * Adjust the `in_use' and `high_water_mark' of `limits'.
461  *
462  * If `prev' is 0, this function behaves like `lim_malloc'.
463  *
464  *
465  * If `limits' is 0, this function works like xref:"must_realloc".
466  *
467  */
468 void *
lim_realloc(alloc_limits limits,void * prev,size_t amt)469 lim_realloc (alloc_limits limits, void * prev, size_t amt)
470 {
471   void * base;
472   size_t prev_amt;
473 
474   if (!limits)
475     return must_realloc (prev, amt);
476 
477   if (!prev)
478     return lim_malloc (limits, amt);
479 
480   base = MEM_TO_HEADER (prev);
481   prev_amt = *(size_t *)base;
482 
483   if (amt > prev_amt)
484     {
485       if (lim_prepare (limits, amt - prev_amt))
486 	{
487 	  if (limits->panic_on_failure)
488 	    {
489 	      panic_msg ("allocation failure");
490 	      panic (limits->name);
491 	    }
492 	  else
493 	    return 0;
494 	}
495     }
496 
497   return lim_soft_realloc (limits, prev, amt);
498 }
499 
500 
501 /*(c lim_soft_realloc)
502  * void * lim_soft_realloc (alloc_limits limits,
503  *                          void * prev,
504  *                          size_t amt);
505  *
506  *
507  * Reallocate `prev' as `amt' bytes of memory.  Copy (up to) `amt' bytes
508  * of data from `prev' to the newly allocated memory.
509  *
510  * If the allocation would exceed the `failure_pt' of `limits', or if
511  * the underlying `malloc' fails, return 0 (if the
512  * `panic_on_failure' flag of `limits' is 0) or exit the
513  * process with a non-0 status.
514  *
515  * Adjust the `in_use' and `high_water_mark' of `limits'.
516  *
517  * This function *does not* invoke the `free_memory' function of
518  * `limits', even if the allocation would exceed the threshold of
519  * `limits'.
520  *
521  * If `limits' is 0, this function works like xref:"must_realloc".
522  *
523  */
524 void *
lim_soft_realloc(alloc_limits limits,void * prev,size_t amt)525 lim_soft_realloc (alloc_limits limits,
526 		  void * prev,
527 		  size_t amt)
528 {
529   void * base;
530   size_t prev_amt;
531   void * answer;
532 
533   if (!limits)
534     return must_realloc (prev, amt);
535 
536   if (!prev)
537     return lim_soft_malloc (limits, amt);
538 
539   base = MEM_TO_HEADER (prev);
540   prev_amt = *(size_t *)base;
541 
542   if (amt > prev_amt)
543     {
544       if (limits->failure_pt && (limits->in_use + amt - prev_amt >= limits->failure_pt))
545 	{
546 	  if (limits->panic_on_failure)
547 	    {
548 	      panic_msg ("allocation failure");
549 	      panic (limits->name);
550 	    }
551 	  else
552 	    return 0;
553 	}
554     }
555 
556   answer = (void *)realloc (base, amt + SIZEOF_HEADER);
557 
558   if (!answer)
559     {
560       if (limits->panic_on_failure)
561 	{
562 	  panic_msg ("allocation failure");
563 	  panic (limits->name);
564 	}
565       else
566 	return 0;
567     }
568 
569   *(size_t *)answer = amt;
570   limits->in_use -= prev_amt;
571   limits->in_use += amt;
572   if (limits->in_use > limits->high_water_mark)
573     limits->high_water_mark = limits->in_use;
574   return HEADER_TO_MEM (answer);
575 }
576 
577 
578 /*(c lim_free)
579  * void lim_free (alloc_limits limits, void * ptr);
580  *
581  * Free `ptr'.  Adjust the `in_use' and `high_water_mark' values
582  * of `limits'.
583  *
584  * If `limits' is 0, this function works like xref:"must_free".
585  *
586  */
587 void
lim_free(alloc_limits limits,void * ptr)588 lim_free (alloc_limits limits, void * ptr)
589 {
590   void * base;
591   size_t amt;
592 
593   if (limits == lim_no_allocations)
594     return;
595 
596   if (!ptr)
597     return;
598 
599   if (!limits)
600     {
601       must_free (ptr);
602       return;
603     }
604   base = MEM_TO_HEADER (ptr);
605   amt = *(size_t *)base;
606   limits->in_use -= amt;
607   free (base);
608 }
609 
610 
611 
612 /************************************************************************
613  *(h2 "Reserving Limited Memory")
614  *
615  *
616  *
617  */
618 
619 
620 /*(c lim_prepare)
621  * int lim_prepare (alloc_limits limits, size_t amt);
622  *
623  * Prepare for an allocation of `amt' bytes.
624  *
625  * If such an allocation would exceed the threshold of `limits',
626  * invoke the `free_memory' function first.
627  *
628  * If the allocation would exceed the `failure_pt' of `limits',
629  * return -1.  Otherwise, return 0.
630  *
631  *
632  * If `limits' is 0, this function simply returns 0.
633  *
634  */
635 int
lim_prepare(alloc_limits limits,size_t amt)636 lim_prepare (alloc_limits limits, size_t amt)
637 {
638   if (!limits)
639     return 0;
640 
641   if (limits->threshold && (limits->in_use + amt >= limits->threshold) && limits->free_memory)
642     limits->free_memory (limits->closure, amt);
643 
644   if (limits->failure_pt && (limits->in_use + amt >= limits->failure_pt))
645     return -1;
646 
647   return 0;
648 }
649 
650 
651 
652 void *
lim_malloc_contiguous(alloc_limits limits,size_t base_size,...)653 lim_malloc_contiguous (alloc_limits limits, size_t base_size, ...)
654 {
655   va_list ap;
656   size_t total_size;
657   size_t part_offset;
658   size_t part_size;
659   t_uchar * answer;
660 
661   total_size = base_size;
662 
663   va_start (ap, base_size);
664   while (1)
665     {
666       part_offset = va_arg (ap, size_t);
667       if (part_offset == (size_t)-1)
668 	break;
669       part_size = va_arg (ap, size_t);
670       total_size += part_size;
671     }
672   va_end (ap);
673 
674   answer = lim_malloc (limits, total_size);
675   if (!answer)
676     return 0;
677 
678   va_start (ap, base_size);
679   total_size = base_size;
680   while (1)
681     {
682       part_offset = va_arg (ap, size_t);
683       if (part_offset == (size_t)-1)
684 	break;
685       part_size = va_arg (ap, size_t);
686       *(t_uchar **)(answer + part_offset) = answer + total_size;
687       total_size += part_size;
688     }
689   return (void *)answer;
690 }
691