diff options
Diffstat (limited to '')
-rw-r--r-- | src/pl_alloc.c | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/src/pl_alloc.c b/src/pl_alloc.c new file mode 100644 index 0000000..64eeda7 --- /dev/null +++ b/src/pl_alloc.c @@ -0,0 +1,313 @@ +/* + * This file is part of libplacebo. + * + * libplacebo is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libplacebo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common.h" + +struct header { +#ifndef NDEBUG +#define MAGIC 0x20210119LU + uint32_t magic; +#endif + size_t size; + struct header *parent; + struct ext *ext; + + // Pointer to actual data, for alignment purposes + max_align_t data[]; +}; + +// Lazily allocated, to save space for leaf allocations and allocations which +// don't need fancy requirements +struct ext { + size_t num_children; + size_t children_size; // total allocated size of `children` + struct header *children[]; +}; + +#define PTR_OFFSET offsetof(struct header, data) +#define MAX_ALLOC (SIZE_MAX - PTR_OFFSET) +#define MINIMUM_CHILDREN 4 + +static inline struct header *get_header(void *ptr) +{ + if (!ptr) + return NULL; + + struct header *hdr = (struct header *) ((uintptr_t) ptr - PTR_OFFSET); +#ifndef NDEBUG + assert(hdr->magic == MAGIC); +#endif + + return hdr; +} + +static inline void *oom(void) +{ + fprintf(stderr, "out of memory\n"); + abort(); +} + +static inline struct ext *alloc_ext(struct header *h) +{ + if (!h) + return NULL; + + if (!h->ext) { + h->ext = malloc(sizeof(struct ext) + MINIMUM_CHILDREN * sizeof(void *)); + if (!h->ext) + oom(); + h->ext->num_children = 0; + h->ext->children_size = MINIMUM_CHILDREN; + } + + return h->ext; +} + +static inline void attach_child(struct header *parent, struct header *child) +{ + child->parent = parent; + if (!parent) + return; + + + struct ext *ext = alloc_ext(parent); + if (ext->num_children == ext->children_size) { + size_t new_size = ext->children_size * 2; + ext = realloc(ext, sizeof(struct ext) + new_size * sizeof(void *)); + if (!ext) + oom(); + ext->children_size = new_size; + parent->ext = ext; + } + + ext->children[ext->num_children++] = child; +} + +static inline void unlink_child(struct header *parent, struct header *child) +{ + child->parent = NULL; + if (!parent) + return; + + struct ext *ext = parent->ext; + for (size_t i = 0; i < ext->num_children; i++) { + if (ext->children[i] == child) { + memmove(&ext->children[i], &ext->children[i + 1], + (--ext->num_children - i) * sizeof(ext->children[0])); + return; + } + } + + assert(!"unlinking orphaned child?"); +} + +void *pl_alloc(void *parent, size_t size) +{ + if (size >= MAX_ALLOC) + return oom(); + + struct header *h = malloc(PTR_OFFSET + size); + if (!h) + return oom(); + +#ifndef NDEBUG + h->magic = MAGIC; +#endif + h->size = size; + h->ext = NULL; + + attach_child(get_header(parent), h); + return h->data; +} + +void *pl_zalloc(void *parent, size_t size) +{ + if (size >= MAX_ALLOC) + return oom(); + + struct header *h = calloc(1, PTR_OFFSET + size); + if (!h) + return oom(); + +#ifndef NDEBUG + h->magic = MAGIC; +#endif + h->size = size; + + attach_child(get_header(parent), h); + return h->data; +} + +void *pl_realloc(void *parent, void *ptr, size_t size) +{ + if (size >= MAX_ALLOC) + return oom(); + if (!ptr) + return pl_alloc(parent, size); + + struct header *h = get_header(ptr); + assert(get_header(parent) == h->parent); + if (h->size == size) + return ptr; + + struct header *old_h = h; + h = realloc(h, PTR_OFFSET + size); + if (!h) + return oom(); + + h->size = size; + + if (h != old_h) { + if (h->parent) { + struct ext *ext = h->parent->ext; + for (size_t i = 0; i < ext->num_children; i++) { + if (ext->children[i] == old_h) { + ext->children[i] = h; + goto done_reparenting; + } + } + assert(!"reallocating orphaned child?"); + } +done_reparenting: + + if (h->ext) { + for (size_t i = 0; i < h->ext->num_children; i++) + h->ext->children[i]->parent = h; + } + } + + return h->data; +} + +void pl_free(void *ptr) +{ + struct header *h = get_header(ptr); + if (!h) + return; + + pl_free_children(ptr); + unlink_child(h->parent, h); + + free(h->ext); + free(h); +} + +void pl_free_children(void *ptr) +{ + struct header *h = get_header(ptr); + if (!h || !h->ext) + return; + +#ifndef NDEBUG + // this detects recursive hierarchies + h->magic = 0; +#endif + + for (size_t i = 0; i < h->ext->num_children; i++) { + h->ext->children[i]->parent = NULL; // prevent recursive access + pl_free(h->ext->children[i]->data); + } + h->ext->num_children = 0; + +#ifndef NDEBUG + h->magic = MAGIC; +#endif +} + +size_t pl_get_size(const void *ptr) +{ + const struct header *h = get_header((void *) ptr); + return h ? h->size : 0; +} + +void *pl_steal(void *parent, void *ptr) +{ + struct header *h = get_header(ptr); + if (!h) + return NULL; + + struct header *new_par = get_header(parent); + if (new_par != h->parent) { + unlink_child(h->parent, h); + attach_child(new_par, h); + } + + return h->data; +} + +void *pl_memdup(void *parent, const void *ptr, size_t size) +{ + if (!size) + return NULL; + + void *new = pl_alloc(parent, size); + if (!new) + return oom(); + + assert(ptr); + memcpy(new, ptr, size); + return new; +} + +char *pl_str0dup0(void *parent, const char *str) +{ + if (!str) + return NULL; + + return pl_memdup(parent, str, strlen(str) + 1); +} + +char *pl_strndup0(void *parent, const char *str, size_t size) +{ + if (!str) + return NULL; + + size_t str_size = strnlen(str, size); + char *new = pl_alloc(parent, str_size + 1); + if (!new) + return oom(); + memcpy(new, str, str_size); + new[str_size] = '\0'; + return new; +} + +char *pl_asprintf(void *parent, const char *fmt, ...) +{ + char *str; + va_list ap; + va_start(ap, fmt); + str = pl_vasprintf(parent, fmt, ap); + va_end(ap); + return str; +} + +char *pl_vasprintf(void *parent, const char *fmt, va_list ap) +{ + // First, we need to determine the size that will be required for + // printing the entire string. Do this by making a copy of the va_list + // and printing it to a null buffer. + va_list copy; + va_copy(copy, ap); + int size = vsnprintf(NULL, 0, fmt, copy); + va_end(copy); + if (size < 0) + return NULL; + + char *str = pl_alloc(parent, size + 1); + vsnprintf(str, size + 1, fmt, ap); + return str; +} |