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