/* tag: Tom Lord Tue Dec 4 14:41:27 2001 (alloc-limits.c) */ /* alloc-limits.c - * **************************************************************** * Copyright (C) 2000 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/machine/alignment.h" #include "hackerlab/os/malloc.h" #include "hackerlab/os/stdarg.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/mem.h" #include "hackerlab/mem/must-malloc.h" #include "hackerlab/mem/alloc-limits.h" /************************************************************************ *(h1 "Allocation With Limitations" * :includes ("hackerlab/mem/alloc-limits.h")) * * |allocation limits| * |limited allocation| * * In some situations it is desriable for a subsystem of a program * to limit its use of memory, independently of the rest of the program. * The `alloc_limits' functions are for such situations. * * There are two steps to using the `alloc_limits' functions. First, * you create an `alloc_limits' object which specifies how much memory * your subsystem should, ideally, use, and how much it is permitted to use. * See xref:"make_alloc_limits". Second, use functions like `lim_malloc' * and `lim_free' instead of functions like `malloc' and `free' in the * subsystem to which the limits apply. * * */ /*(menu) */ #define SIZEOF_HEADER ((sizeof (size_t) > MACHINE_ALIGNMENT) ? sizeof (size_t) : MACHINE_ALIGNMENT) #define MEM_TO_HEADER(B) ((size_t *)((char *)(B) - SIZEOF_HEADER)) #define HEADER_TO_MEM(H) ((void *)((char *)(H) + SIZEOF_HEADER)) struct alloc_limits { t_uchar * name; size_t threshold; /* where does allocation trigger gc? */ size_t failure_pt; /* where does allocation fail? */ /* threshold == 0 never trigger gc * failure_pt == 0 never fail */ int panic_on_failure; size_t in_use; size_t high_water_mark; lim_free_memory_fn free_memory; void * closure; }; struct alloc_limits lim_use_malloc_limits = { "alloc_limits for generic malloc", 0, 0, 0, 0, 0, 0, 0 }; struct alloc_limits lim_no_allocations_limits = { "alloc_limits which prohibit allocation", 0, 1, 0, 1, 1, 0, 0 }; /************************************************************************ *(h2 "Specifying Allocation Limits") * * An object of the opaque type `alloc_limits' records the rules which * limit memory use in a particular subsystem. * */ /*(c make_alloc_limits) * 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); * * Create a new `alloc_limits' object. * * `name' is the name of the subsystem to which these allocation limits * apply. The name is useful for debugging purposes. * * `threshold' specifies an ideal limit on the amount of memory used * by your subsystem. If a subsystem attempts to allocate more than * `threshold' bytes, the `free_memory' function for that subsystem * is invoked (see below). `threshold' may be 0, in which case the * `free_memory' function is never invoked. * * `failure_pt' specifies an absolute limit on the amount of memory * used by your subsystem. Allocations beyond `failure_pt' fail * (return 0). * * `panic_on_failure', if non-0, means that if the failure point is * reached, or allocation fails, the program will exit with a * message to the standard error stream and a non-0 status. * If 0, allocation failures return 0. * * |$lim_free_memory_fn| * * `free_memory' is a function pointer: * * typedef void (*lim_free_memory_fn)(void * closure, * size_t needed); * * It is called immediately before an allocation that would exceed * `threshold' (if `threshold' is not 0). `closure' is as passed * to `make_alloc_limits' (see below). `needed' is the number of * bytes by which the proposed allocation causes the total amount of * memory used by the subsystem to exceed `threshold'. It is completely * safe to call `lim_free' from your `free_memory' function. It * is possible to call `lim_malloc' or `lim_realloc', but if your * program does this, `free_memory' must be reentrant. * * `closure' is an opaque value passed to `free_memory'. * * If a new `alloc_limits' object can not be allocated, this function * calls `panic' and does not return. * * As an alternative to calling `make_alloc_limits' in some situations, * three pre-defined allocation limits are declared in `alloc-limits.h': * * extern alloc_limits lim_use_malloc; * extern alloc_limits lim_use_must_malloc; * extern alloc_limits lim_no_allocations; * * Allocations performed with `lim_use_malloc' are unlimited and are * effectively like allocations performed with `malloc' or `realloc'. * Failed allocations return 0. * * Allocations performed with `lim_use_must_malloc' are unlimited and * are effectively like allocations performed with `must_malloc' or * `must_realloc'. Failed allocations cause the process to exit with * a non-0 status. * * Allocations performed with `lim_no_allocations' always fail and return * 0. `lim_free' has no effect when passed `lim_no_allocations'. */ 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) { alloc_limits answer; answer = (alloc_limits)must_malloc (sizeof (struct alloc_limits)); answer->name = name; answer->threshold = threshold; answer->failure_pt = failure_pt; answer->panic_on_failure = panic_on_failure; answer->in_use = 0; answer->high_water_mark = 0; answer->free_memory = free_memory; answer->closure = closure; return answer; } /*(c free_alloc_limits) * void free_alloc_limits (alloc_limits limits); * * Free an allocation limits object. */ void free_alloc_limits (alloc_limits limits) { must_free ((void *)limits); } /*(c lim_set_threshold) * size_t lim_set_threshold (alloc_limits it, size_t threshold); * * Modify the `threshold' of an `alloc_limits' object. Return * the old `threshold'. * * This function does not immediately call `free_memory', even if * the total amount of memory allocated exceeds the new `threshold'. */ size_t lim_set_threshold (alloc_limits it, size_t threshold) { size_t old; old = it->threshold; it->threshold = threshold; return old; } /*(c lim_threshold) * size_t lim_threshold (alloc_limits limits); * * Return the current `threshold' of allocation limits `limits'. */ size_t lim_threshold (alloc_limits limits) { return limits->threshold; } /*(c lim_set_failure_pt) * size_t lim_set_failure_pt (alloc_limits limits, size_t failure_pt); * * Modify the `failure_pt' of an `alloc_limits' object. Return * the old `failure_pt'. * */ size_t lim_set_failure_pt (alloc_limits limits, size_t failure_pt) { size_t old; old = limits->failure_pt; limits->failure_pt = failure_pt; return old; } /*(c lim_failure_pt) * size_t lim_failure_pt (alloc_limits limits); * * Return the `failure_pt' of allocation limits `limits'. */ size_t lim_failure_pt (alloc_limits limits) { return limits->failure_pt; } /*(c lim_is_panic_on_failure) * int lim_is_panic_on_failure (alloc_limits limits); * * Return the value of the `panic_on_failure' flag of * allocation limits `limits'. */ int lim_is_panic_on_failure (alloc_limits limits) { return limits->panic_on_failure; } /*(c lim_set_panic_on_failure) * int lim_set_panic_on_failure (alloc_limits limits, int value); * * Set the `panic_on_failure' flag of allocation limits `limits'. * * Return the old value. */ int lim_set_panic_on_failure (alloc_limits limits, int value) { int was; was = limits->panic_on_failure; limits->panic_on_failure = value; return was; } /*(c lim_in_use) * size_t lim_in_use (alloc_limits limits); * * Return the amount (in bytes) of memory allocation charged * to `limits' and not yet freed. */ size_t lim_in_use (alloc_limits limits) { return limits->in_use; } /*(c lim_high_water_mark) * size_t lim_high_water_mark (alloc_limits limits); * * Return the largest amount (in bytes) of outstanding memory * allocation charged to `limits' during the lifetime of the process. */ size_t lim_high_water_mark (alloc_limits limits) { return limits->high_water_mark; } /************************************************************************ *(h2 "Allocating and Freeing") * * These functions allocate and free memory, similarly to * `malloc', `realloc', and `free', but with allocation limits. * */ /*(c lim_malloc) * void * lim_malloc (alloc_limits limits, size_t amt); * * Allocate `amt' bytes of memory. * * If the allocation would exceed the threshold of `limits', * invoke the `free_memory' function first. * * If the allocation would exceed the `failure_pt' of `limits', or if * the underlying `malloc' fails, return 0 (if the `panic_on_failure' * flag of `limits' is 0) or exit the process with a non-0 status (if * `panic_on_failure' is non-0). * * Adjust the `in_use' and `high_water_mark' of `limits'. * * If `limits' is 0, this function works like xref:"must_malloc". * */ void * lim_malloc (alloc_limits limits, size_t amt) { if (!limits) return must_malloc (amt); if (lim_prepare (limits, amt)) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } return lim_soft_malloc (limits, amt); } /*(c lim_zalloc) * void * lim_zalloc (alloc_limits limits, size_t amt); * * Use `lim_malloc' to attempt to allocate `amt' bytes * of memory. If allocation succeeds, initialize that * memory to all zero bytes. * * Return the allocated region or 0 if allocation fails. */ void * lim_zalloc (alloc_limits limits, size_t amt) { void * answer; answer = lim_malloc (limits, amt); if (answer) mem_set0 ((t_uchar *)answer, amt); return answer; } /*(c lim_soft_malloc) * void * lim_soft_malloc (alloc_limits limits, size_t amt); * * Allocate `amt' bytes of memory. Return the newly allocated memory. * * If the allocation would exceed the `failure_pt' of `limits', * or if the underlying `malloc' fails, return 0 (if the * `panic_on_failure' flag of `limits' is 0) or exit the * process with a non-0 status. * * Adjust the `in_use' and `high_water_mark' of `limits'. * * This function *does not* invoke the `free_memory' function of * `limits', even if the allocation would exceed the threshold of * `limits'. * * If `limits' is 0, this function works like xref:"must_malloc". * */ void * lim_soft_malloc (alloc_limits limits, size_t amt) { void * answer; if (!limits) return must_malloc (amt); if (limits->failure_pt && (limits->in_use + amt >= limits->failure_pt)) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } answer = (void *)malloc (amt + SIZEOF_HEADER); if (!answer) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } *(size_t *)answer = amt; limits->in_use += amt; if (limits->in_use > limits->high_water_mark) limits->high_water_mark = limits->in_use; return HEADER_TO_MEM (answer); } /*(c lim_realloc) * void * lim_realloc (alloc_limits limits, void * prev, size_t amt); * * Reallocate `prev' as `amt' bytes of memory. Copy (up to) `amt' bytes * of data from `prev' to the newly allocated memory. * * If the allocation would exceed the threshold of `limits', * invoke the `free_memory' function first. * * If the allocation would exceed the `failure_pt' of `limits', * or if the underlying `malloc' fails, return 0 (if the * `panic_on_failure' flag of `limits' is 0) or exit the * process with a non-0 status. * * Adjust the `in_use' and `high_water_mark' of `limits'. * * If `prev' is 0, this function behaves like `lim_malloc'. * * * If `limits' is 0, this function works like xref:"must_realloc". * */ void * lim_realloc (alloc_limits limits, void * prev, size_t amt) { void * base; size_t prev_amt; if (!limits) return must_realloc (prev, amt); if (!prev) return lim_malloc (limits, amt); base = MEM_TO_HEADER (prev); prev_amt = *(size_t *)base; if (amt > prev_amt) { if (lim_prepare (limits, amt - prev_amt)) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } } return lim_soft_realloc (limits, prev, amt); } /*(c lim_soft_realloc) * void * lim_soft_realloc (alloc_limits limits, * void * prev, * size_t amt); * * * Reallocate `prev' as `amt' bytes of memory. Copy (up to) `amt' bytes * of data from `prev' to the newly allocated memory. * * If the allocation would exceed the `failure_pt' of `limits', or if * the underlying `malloc' fails, return 0 (if the * `panic_on_failure' flag of `limits' is 0) or exit the * process with a non-0 status. * * Adjust the `in_use' and `high_water_mark' of `limits'. * * This function *does not* invoke the `free_memory' function of * `limits', even if the allocation would exceed the threshold of * `limits'. * * If `limits' is 0, this function works like xref:"must_realloc". * */ void * lim_soft_realloc (alloc_limits limits, void * prev, size_t amt) { void * base; size_t prev_amt; void * answer; if (!limits) return must_realloc (prev, amt); if (!prev) return lim_soft_malloc (limits, amt); base = MEM_TO_HEADER (prev); prev_amt = *(size_t *)base; if (amt > prev_amt) { if (limits->failure_pt && (limits->in_use + amt - prev_amt >= limits->failure_pt)) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } } answer = (void *)realloc (base, amt + SIZEOF_HEADER); if (!answer) { if (limits->panic_on_failure) { panic_msg ("allocation failure"); panic (limits->name); } else return 0; } *(size_t *)answer = amt; limits->in_use -= prev_amt; limits->in_use += amt; if (limits->in_use > limits->high_water_mark) limits->high_water_mark = limits->in_use; return HEADER_TO_MEM (answer); } /*(c lim_free) * void lim_free (alloc_limits limits, void * ptr); * * Free `ptr'. Adjust the `in_use' and `high_water_mark' values * of `limits'. * * If `limits' is 0, this function works like xref:"must_free". * */ void lim_free (alloc_limits limits, void * ptr) { void * base; size_t amt; if (limits == lim_no_allocations) return; if (!ptr) return; if (!limits) { must_free (ptr); return; } base = MEM_TO_HEADER (ptr); amt = *(size_t *)base; limits->in_use -= amt; free (base); } /************************************************************************ *(h2 "Reserving Limited Memory") * * * */ /*(c lim_prepare) * int lim_prepare (alloc_limits limits, size_t amt); * * Prepare for an allocation of `amt' bytes. * * If such an allocation would exceed the threshold of `limits', * invoke the `free_memory' function first. * * If the allocation would exceed the `failure_pt' of `limits', * return -1. Otherwise, return 0. * * * If `limits' is 0, this function simply returns 0. * */ int lim_prepare (alloc_limits limits, size_t amt) { if (!limits) return 0; if (limits->threshold && (limits->in_use + amt >= limits->threshold) && limits->free_memory) limits->free_memory (limits->closure, amt); if (limits->failure_pt && (limits->in_use + amt >= limits->failure_pt)) return -1; return 0; } void * lim_malloc_contiguous (alloc_limits limits, size_t base_size, ...) { va_list ap; size_t total_size; size_t part_offset; size_t part_size; t_uchar * answer; total_size = base_size; va_start (ap, base_size); while (1) { part_offset = va_arg (ap, size_t); if (part_offset == (size_t)-1) break; part_size = va_arg (ap, size_t); total_size += part_size; } va_end (ap); answer = lim_malloc (limits, total_size); if (!answer) return 0; va_start (ap, base_size); total_size = base_size; while (1) { part_offset = va_arg (ap, size_t); if (part_offset == (size_t)-1) break; part_size = va_arg (ap, size_t); *(t_uchar **)(answer + part_offset) = answer + total_size; total_size += part_size; } return (void *)answer; }