#include "upb/env.h" #include #include #include typedef struct cleanup_ent { upb_cleanup_func *cleanup; void *ud; struct cleanup_ent *next; } cleanup_ent; static void *seeded_alloc(void *ud, void *ptr, size_t oldsize, size_t size); /* Default allocator **********************************************************/ /* Just use realloc, keeping all allocated blocks in a linked list to destroy at * the end. */ typedef struct mem_block { /* List is doubly-linked, because in cases where realloc() moves an existing * block, we need to be able to remove the old pointer from the list * efficiently. */ struct mem_block *prev, *next; #ifndef NDEBUG size_t size; /* Doesn't include mem_block structure. */ #endif } mem_block; typedef struct { mem_block *head; } default_alloc_ud; static void *default_alloc(void *_ud, void *ptr, size_t oldsize, size_t size) { default_alloc_ud *ud = _ud; mem_block *from, *block; void *ret; UPB_UNUSED(oldsize); from = ptr ? (void*)((char*)ptr - sizeof(mem_block)) : NULL; #ifndef NDEBUG if (from) { assert(oldsize <= from->size); } #endif /* TODO(haberman): we probably need to provide even better alignment here, * like 16-byte alignment of the returned data pointer. */ block = realloc(from, size + sizeof(mem_block)); if (!block) return NULL; ret = (char*)block + sizeof(*block); #ifndef NDEBUG block->size = size; #endif if (from) { if (block != from) { /* The block was moved, so pointers in next and prev blocks must be * updated to its new location. */ if (block->next) block->next->prev = block; if (block->prev) block->prev->next = block; if (ud->head == from) ud->head = block; } } else { /* Insert at head of linked list. */ block->prev = NULL; block->next = ud->head; if (block->next) block->next->prev = block; ud->head = block; } return ret; } static void default_alloc_cleanup(void *_ud) { default_alloc_ud *ud = _ud; mem_block *block = ud->head; while (block) { void *to_free = block; block = block->next; free(to_free); } } /* Standard error functions ***************************************************/ static bool default_err(void *ud, const upb_status *status) { UPB_UNUSED(ud); UPB_UNUSED(status); return false; } static bool write_err_to(void *ud, const upb_status *status) { upb_status *copy_to = ud; upb_status_copy(copy_to, status); return false; } /* upb_env ********************************************************************/ void upb_env_init(upb_env *e) { default_alloc_ud *ud = (default_alloc_ud*)&e->default_alloc_ud; e->ok_ = true; e->bytes_allocated = 0; e->cleanup_head = NULL; ud->head = NULL; /* Set default functions. */ upb_env_setallocfunc(e, default_alloc, ud); upb_env_seterrorfunc(e, default_err, NULL); } void upb_env_uninit(upb_env *e) { cleanup_ent *ent = e->cleanup_head; while (ent) { ent->cleanup(ent->ud); ent = ent->next; } /* Must do this after running cleanup functions, because this will delete the memory we store our cleanup entries in! */ if (e->alloc == default_alloc) { default_alloc_cleanup(e->alloc_ud); } } UPB_FORCEINLINE void upb_env_setallocfunc(upb_env *e, upb_alloc_func *alloc, void *ud) { e->alloc = alloc; e->alloc_ud = ud; } UPB_FORCEINLINE void upb_env_seterrorfunc(upb_env *e, upb_error_func *func, void *ud) { e->err = func; e->err_ud = ud; } void upb_env_reporterrorsto(upb_env *e, upb_status *status) { e->err = write_err_to; e->err_ud = status; } bool upb_env_ok(const upb_env *e) { return e->ok_; } bool upb_env_reporterror(upb_env *e, const upb_status *status) { e->ok_ = false; return e->err(e->err_ud, status); } bool upb_env_addcleanup(upb_env *e, upb_cleanup_func *func, void *ud) { cleanup_ent *ent = upb_env_malloc(e, sizeof(cleanup_ent)); if (!ent) return false; ent->cleanup = func; ent->ud = ud; ent->next = e->cleanup_head; e->cleanup_head = ent; return true; } void *upb_env_malloc(upb_env *e, size_t size) { e->bytes_allocated += size; if (e->alloc == seeded_alloc) { /* This is equivalent to the next branch, but allows inlining for a * measurable perf benefit. */ return seeded_alloc(e->alloc_ud, NULL, 0, size); } else { return e->alloc(e->alloc_ud, NULL, 0, size); } } void *upb_env_realloc(upb_env *e, void *ptr, size_t oldsize, size_t size) { char *ret; assert(oldsize <= size); ret = e->alloc(e->alloc_ud, ptr, oldsize, size); #ifndef NDEBUG /* Overwrite non-preserved memory to ensure callers are passing the oldsize * that they truly require. */ memset(ret + oldsize, 0xff, size - oldsize); #endif return ret; } size_t upb_env_bytesallocated(const upb_env *e) { return e->bytes_allocated; } /* upb_seededalloc ************************************************************/ /* Be conservative and choose 16 in case anyone is using SSE. */ static const size_t maxalign = 16; static size_t align_up(size_t size) { return ((size + maxalign - 1) / maxalign) * maxalign; } UPB_FORCEINLINE static void *seeded_alloc(void *ud, void *ptr, size_t oldsize, size_t size) { upb_seededalloc *a = ud; size = align_up(size); assert(a->mem_limit >= a->mem_ptr); if (oldsize == 0 && size <= (size_t)(a->mem_limit - a->mem_ptr)) { /* Fast path: we can satisfy from the initial allocation. */ void *ret = a->mem_ptr; a->mem_ptr += size; return ret; } else { char *chptr = ptr; /* Slow path: fallback to other allocator. */ a->need_cleanup = true; /* Is `ptr` part of the user-provided initial block? Don't pass it to the * default allocator if so; otherwise, it may try to realloc() the block. */ if (chptr >= a->mem_base && chptr < a->mem_limit) { void *ret; assert(chptr + oldsize <= a->mem_limit); ret = a->alloc(a->alloc_ud, NULL, 0, size); if (ret) memcpy(ret, ptr, oldsize); return ret; } else { return a->alloc(a->alloc_ud, ptr, oldsize, size); } } } void upb_seededalloc_init(upb_seededalloc *a, void *mem, size_t len) { default_alloc_ud *ud = (default_alloc_ud*)&a->default_alloc_ud; a->mem_base = mem; a->mem_ptr = mem; a->mem_limit = (char*)mem + len; a->need_cleanup = false; a->returned_allocfunc = false; ud->head = NULL; upb_seededalloc_setfallbackalloc(a, default_alloc, ud); } void upb_seededalloc_uninit(upb_seededalloc *a) { if (a->alloc == default_alloc && a->need_cleanup) { default_alloc_cleanup(a->alloc_ud); } } UPB_FORCEINLINE void upb_seededalloc_setfallbackalloc(upb_seededalloc *a, upb_alloc_func *alloc, void *ud) { assert(!a->returned_allocfunc); a->alloc = alloc; a->alloc_ud = ud; } upb_alloc_func *upb_seededalloc_getallocfunc(upb_seededalloc *a) { a->returned_allocfunc = true; return seeded_alloc; }