#include "libc/mem/mem.h" #include "libc/str/str.h" #include "third_party/dlmalloc/dlmalloc.internal.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(g_dlmalloc, 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(g_dlmalloc, n_elements, sizes, 0, chunks); }