229 lines
8.5 KiB
C
229 lines
8.5 KiB
C
|
#include "libc/mem/mem.h"
|
||
|
#include "libc/str/str.h"
|
||
|
#include "third_party/dlmalloc/dlmalloc.h"
|
||
|
|
||
|
/*
|
||
|
Common support for independent_X routines, handling
|
||
|
all of the combinations that can result.
|
||
|
The opts arg has:
|
||
|
bit 0 set if all elements are same size (using sizes[0])
|
||
|
bit 1 set if elements should be zeroed
|
||
|
*/
|
||
|
static void **ialloc(mstate m, size_t n_elements, size_t *sizes, int opts,
|
||
|
void *chunks[]) {
|
||
|
size_t element_size; /* chunksize of each element, if all same */
|
||
|
size_t contents_size; /* total size of elements */
|
||
|
size_t array_size; /* request size of pointer array */
|
||
|
void *mem; /* malloced aggregate space */
|
||
|
mchunkptr p; /* corresponding chunk */
|
||
|
size_t remainder_size; /* remaining bytes while splitting */
|
||
|
void **marray; /* either "chunks" or malloced ptr array */
|
||
|
mchunkptr array_chunk; /* chunk for malloced ptr array */
|
||
|
flag_t was_enabled; /* to disable mmap */
|
||
|
size_t size;
|
||
|
size_t i;
|
||
|
|
||
|
ensure_initialization();
|
||
|
/* compute array length, if needed */
|
||
|
if (chunks != 0) {
|
||
|
if (n_elements == 0) return chunks; /* nothing to do */
|
||
|
marray = chunks;
|
||
|
array_size = 0;
|
||
|
} else {
|
||
|
/* if empty req, must still return chunk representing empty array */
|
||
|
if (n_elements == 0) return (void **)dlmalloc(0);
|
||
|
marray = 0;
|
||
|
array_size = request2size(n_elements * (sizeof(void *)));
|
||
|
}
|
||
|
|
||
|
/* compute total element size */
|
||
|
if (opts & 0x1) { /* all-same-size */
|
||
|
element_size = request2size(*sizes);
|
||
|
contents_size = n_elements * element_size;
|
||
|
} else { /* add up all the sizes */
|
||
|
element_size = 0;
|
||
|
contents_size = 0;
|
||
|
for (i = 0; i != n_elements; ++i) contents_size += request2size(sizes[i]);
|
||
|
}
|
||
|
|
||
|
size = contents_size + array_size;
|
||
|
|
||
|
/*
|
||
|
Allocate the aggregate chunk. First disable direct-mmapping so
|
||
|
malloc won't use it, since we would not be able to later
|
||
|
free/realloc space internal to a segregated mmap region.
|
||
|
*/
|
||
|
was_enabled = use_mmap(m);
|
||
|
disable_mmap(m);
|
||
|
mem = dlmalloc(size - CHUNK_OVERHEAD);
|
||
|
if (was_enabled) enable_mmap(m);
|
||
|
if (mem == 0) return 0;
|
||
|
|
||
|
if (PREACTION(m)) return 0;
|
||
|
p = mem2chunk(mem);
|
||
|
remainder_size = chunksize(p);
|
||
|
|
||
|
assert(!is_mmapped(p));
|
||
|
|
||
|
if (opts & 0x2) { /* optionally clear the elements */
|
||
|
memset((size_t *)mem, 0, remainder_size - SIZE_T_SIZE - array_size);
|
||
|
}
|
||
|
|
||
|
/* If not provided, allocate the pointer array as final part of chunk */
|
||
|
if (marray == 0) {
|
||
|
size_t array_chunk_size;
|
||
|
array_chunk = chunk_plus_offset(p, contents_size);
|
||
|
array_chunk_size = remainder_size - contents_size;
|
||
|
marray = ADDRESS_BIRTH_ACTION((void **)(chunk2mem(array_chunk)));
|
||
|
set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size);
|
||
|
remainder_size = contents_size;
|
||
|
}
|
||
|
|
||
|
/* split out elements */
|
||
|
for (i = 0;; ++i) {
|
||
|
marray[i] = ADDRESS_BIRTH_ACTION(chunk2mem(p));
|
||
|
if (i != n_elements - 1) {
|
||
|
if (element_size != 0)
|
||
|
size = element_size;
|
||
|
else
|
||
|
size = request2size(sizes[i]);
|
||
|
remainder_size -= size;
|
||
|
set_size_and_pinuse_of_inuse_chunk(m, p, size);
|
||
|
p = chunk_plus_offset(p, size);
|
||
|
} else { /* the final element absorbs any overallocation slop */
|
||
|
set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if DEBUG + MODE_DBG + 0
|
||
|
if (marray != chunks) {
|
||
|
/* final element must have exactly exhausted chunk */
|
||
|
if (element_size != 0) {
|
||
|
assert(remainder_size == element_size);
|
||
|
} else {
|
||
|
assert(remainder_size == request2size(sizes[i]));
|
||
|
}
|
||
|
check_inuse_chunk(m, mem2chunk(marray));
|
||
|
}
|
||
|
for (i = 0; i != n_elements; ++i) check_inuse_chunk(m, mem2chunk(marray[i]));
|
||
|
|
||
|
#endif /* DEBUG */
|
||
|
|
||
|
POSTACTION(m);
|
||
|
return marray;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* independent_calloc(size_t n_elements, size_t element_size, void* chunks[]);
|
||
|
*
|
||
|
* independent_calloc is similar to calloc, but instead of returning a
|
||
|
* single cleared space, it returns an array of pointers to n_elements
|
||
|
* independent elements that can hold contents of size elem_size, each
|
||
|
* of which starts out cleared, and can be independently freed,
|
||
|
* realloc'ed etc. The elements are guaranteed to be adjacently
|
||
|
* allocated (this is not guaranteed to occur with multiple callocs or
|
||
|
* mallocs), which may also improve cache locality in some applications.
|
||
|
*
|
||
|
* The "chunks" argument is optional (i.e., may be null, which is
|
||
|
* probably the most typical usage). If it is null, the returned array
|
||
|
* is itself dynamically allocated and should also be freed when it is
|
||
|
* no longer needed. Otherwise, the chunks array must be of at least
|
||
|
* n_elements in length. It is filled in with the pointers to the
|
||
|
* chunks.
|
||
|
*
|
||
|
* In either case, independent_calloc returns this pointer array, or
|
||
|
* null if the allocation failed. * If n_elements is zero and "chunks"
|
||
|
* is null, it returns a chunk representing an array with zero elements
|
||
|
* (which should be freed if not wanted).
|
||
|
*
|
||
|
* Each element must be freed when it is no longer needed. This can be
|
||
|
* done all at once using bulk_free.
|
||
|
*
|
||
|
* independent_calloc simplifies and speeds up implementations of many
|
||
|
* kinds of pools. * It may also be useful when constructing large data
|
||
|
* structures that initially have a fixed number of fixed-sized nodes,
|
||
|
* but the number is not known at compile time, and some of the nodes
|
||
|
* may later need to be freed. For example:
|
||
|
*
|
||
|
* struct Node { int item; struct Node* next; };
|
||
|
* struct Node* build_list() {
|
||
|
* struct Node **pool;
|
||
|
* int n = read_number_of_nodes_needed();
|
||
|
* if (n <= 0) return 0;
|
||
|
* pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);
|
||
|
* if (pool == 0) die();
|
||
|
* // organize into a linked list...
|
||
|
* struct Node* first = pool[0];
|
||
|
* for (i = 0; i < n-1; ++i)
|
||
|
* pool[i]->next = pool[i+1];
|
||
|
* free(pool); * // Can now free the array (or not, if it is needed later)
|
||
|
* return first;
|
||
|
* }
|
||
|
*/
|
||
|
void **dlindependent_calloc(size_t n_elements, size_t elem_size,
|
||
|
void *chunks[]) {
|
||
|
size_t sz = elem_size; /* serves as 1-element array */
|
||
|
return ialloc(gm, n_elements, &sz, 3, chunks);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);
|
||
|
*
|
||
|
* independent_comalloc allocates, all at once, a set of n_elements
|
||
|
* chunks with sizes indicated in the "sizes" array. It returns an array
|
||
|
* of pointers to these elements, each of which can be independently
|
||
|
* freed, realloc'ed etc. The elements are guaranteed to be adjacently
|
||
|
* allocated (this is not guaranteed to occur with multiple callocs or
|
||
|
* mallocs), which may also improve cache locality in some applications.
|
||
|
*
|
||
|
* The "chunks" argument is optional (i.e., may be null). If it is null
|
||
|
* the returned array is itself dynamically allocated and should also
|
||
|
* be freed when it is no longer needed. Otherwise, the chunks array
|
||
|
* must be of at least n_elements in length. It is filled in with the
|
||
|
* pointers to the chunks.
|
||
|
*
|
||
|
* In either case, independent_comalloc returns this pointer array, or
|
||
|
* null if the allocation failed. If n_elements is zero and chunks is
|
||
|
* null, it returns a chunk representing an array with zero elements
|
||
|
* (which should be freed if not wanted).
|
||
|
*
|
||
|
* Each element must be freed when it is no longer needed. This can be
|
||
|
* done all at once using bulk_free.
|
||
|
*
|
||
|
* independent_comallac differs from independent_calloc in that each
|
||
|
* element may have a different size, and also that it does not
|
||
|
* automatically clear elements.
|
||
|
*
|
||
|
* independent_comalloc can be used to speed up allocation in cases
|
||
|
* where several structs or objects must always be allocated at the
|
||
|
* same time. For example:
|
||
|
*
|
||
|
* struct Head { ... }
|
||
|
* struct Foot { ... }
|
||
|
*
|
||
|
* void send_message(char* msg) {
|
||
|
* int msglen = strlen(msg);
|
||
|
* size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };
|
||
|
* void* chunks[3];
|
||
|
* if (independent_comalloc(3, sizes, chunks) == 0)
|
||
|
* die();
|
||
|
* struct Head* head = (struct Head*)(chunks[0]);
|
||
|
* char* body = (char*)(chunks[1]);
|
||
|
* struct Foot* foot = (struct Foot*)(chunks[2]);
|
||
|
* // ...
|
||
|
* }
|
||
|
*
|
||
|
* In general though, independent_comalloc is worth using only for
|
||
|
* larger values of n_elements. For small values, you probably won't
|
||
|
* detect enough difference from series of malloc calls to bother.
|
||
|
*
|
||
|
* Overuse of independent_comalloc can increase overall memory usage,
|
||
|
* since it cannot reuse existing noncontiguous small chunks that might
|
||
|
* be available for some of the elements.
|
||
|
*/
|
||
|
void **dlindependent_comalloc(size_t n_elements, size_t sizes[],
|
||
|
void *chunks[]) {
|
||
|
return ialloc(gm, n_elements, sizes, 0, chunks);
|
||
|
}
|