|
|
|
/*
|
|
|
|
* Copyright (c) 2009-2021, Google LLC
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
* * Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Google LLC nor the
|
|
|
|
* names of its contributors may be used to endorse or promote products
|
|
|
|
* derived from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
* DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY
|
|
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "upb/upb_internal.h"
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "upb/port_def.inc"
|
|
|
|
|
|
|
|
/* upb_status *****************************************************************/
|
|
|
|
|
|
|
|
void upb_status_clear(upb_status *status) {
|
|
|
|
if (!status) return;
|
|
|
|
status->ok = true;
|
|
|
|
status->msg[0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
bool upb_ok(const upb_status *status) { return status->ok; }
|
|
|
|
|
|
|
|
const char *upb_status_errmsg(const upb_status *status) { return status->msg; }
|
|
|
|
|
|
|
|
void upb_status_seterrmsg(upb_status *status, const char *msg) {
|
|
|
|
if (!status) return;
|
|
|
|
status->ok = false;
|
|
|
|
strncpy(status->msg, msg, UPB_STATUS_MAX_MESSAGE - 1);
|
|
|
|
status->msg[UPB_STATUS_MAX_MESSAGE - 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
void upb_status_seterrf(upb_status *status, const char *fmt, ...) {
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
|
|
upb_status_vseterrf(status, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
void upb_status_vseterrf(upb_status *status, const char *fmt, va_list args) {
|
|
|
|
if (!status) return;
|
|
|
|
status->ok = false;
|
|
|
|
vsnprintf(status->msg, sizeof(status->msg), fmt, args);
|
|
|
|
status->msg[UPB_STATUS_MAX_MESSAGE - 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
void upb_status_vappenderrf(upb_status *status, const char *fmt, va_list args) {
|
|
|
|
size_t len;
|
|
|
|
if (!status) return;
|
|
|
|
status->ok = false;
|
|
|
|
len = strlen(status->msg);
|
|
|
|
vsnprintf(status->msg + len, sizeof(status->msg) - len, fmt, args);
|
|
|
|
status->msg[UPB_STATUS_MAX_MESSAGE - 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* upb_alloc ******************************************************************/
|
|
|
|
|
|
|
|
static void *upb_global_allocfunc(upb_alloc *alloc, void *ptr, size_t oldsize,
|
|
|
|
size_t size) {
|
|
|
|
UPB_UNUSED(alloc);
|
|
|
|
UPB_UNUSED(oldsize);
|
|
|
|
if (size == 0) {
|
|
|
|
free(ptr);
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
return realloc(ptr, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t *upb_cleanup_pointer(uintptr_t cleanup_metadata) {
|
|
|
|
return (uint32_t *)(cleanup_metadata & ~0x1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool upb_cleanup_has_initial_block(uintptr_t cleanup_metadata) {
|
|
|
|
return cleanup_metadata & 0x1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uintptr_t upb_cleanup_metadata(uint32_t *cleanup,
|
|
|
|
bool has_initial_block) {
|
|
|
|
return (uintptr_t)cleanup | has_initial_block;
|
|
|
|
}
|
|
|
|
|
|
|
|
upb_alloc upb_alloc_global = {&upb_global_allocfunc};
|
|
|
|
|
|
|
|
/* upb_arena ******************************************************************/
|
|
|
|
|
|
|
|
/* Be conservative and choose 16 in case anyone is using SSE. */
|
|
|
|
|
|
|
|
struct mem_block {
|
|
|
|
struct mem_block *next;
|
|
|
|
uint32_t size;
|
|
|
|
uint32_t cleanups;
|
|
|
|
/* Data follows. */
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct cleanup_ent {
|
|
|
|
upb_cleanup_func *cleanup;
|
|
|
|
void *ud;
|
|
|
|
} cleanup_ent;
|
|
|
|
|
|
|
|
static const size_t memblock_reserve = UPB_ALIGN_UP(sizeof(mem_block), 16);
|
|
|
|
|
|
|
|
static upb_arena *arena_findroot(upb_arena *a) {
|
|
|
|
/* Path splitting keeps time complexity down, see:
|
|
|
|
* https://en.wikipedia.org/wiki/Disjoint-set_data_structure */
|
|
|
|
while (a->parent != a) {
|
|
|
|
upb_arena *next = a->parent;
|
|
|
|
a->parent = next->parent;
|
|
|
|
a = next;
|
|
|
|
}
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void upb_arena_addblock(upb_arena *a, upb_arena *root, void *ptr,
|
|
|
|
size_t size) {
|
|
|
|
mem_block *block = ptr;
|
|
|
|
|
|
|
|
/* The block is for arena |a|, but should appear in the freelist of |root|. */
|
|
|
|
block->next = root->freelist;
|
|
|
|
block->size = (uint32_t)size;
|
|
|
|
block->cleanups = 0;
|
|
|
|
root->freelist = block;
|
|
|
|
a->last_size = block->size;
|
|
|
|
if (!root->freelist_tail) root->freelist_tail = block;
|
|
|
|
|
|
|
|
a->head.ptr = UPB_PTR_AT(block, memblock_reserve, char);
|
|
|
|
a->head.end = UPB_PTR_AT(block, size, char);
|
|
|
|
a->cleanup_metadata = upb_cleanup_metadata(
|
|
|
|
&block->cleanups, upb_cleanup_has_initial_block(a->cleanup_metadata));
|
|
|
|
|
|
|
|
UPB_POISON_MEMORY_REGION(a->head.ptr, a->head.end - a->head.ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool upb_arena_allocblock(upb_arena *a, size_t size) {
|
|
|
|
upb_arena *root = arena_findroot(a);
|
|
|
|
size_t block_size = UPB_MAX(size, a->last_size * 2) + memblock_reserve;
|
|
|
|
mem_block *block = upb_malloc(root->block_alloc, block_size);
|
|
|
|
|
|
|
|
if (!block) return false;
|
|
|
|
upb_arena_addblock(a, root, block, block_size);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *_upb_arena_slowmalloc(upb_arena *a, size_t size) {
|
|
|
|
if (!upb_arena_allocblock(a, size)) return NULL; /* Out of memory. */
|
|
|
|
UPB_ASSERT(_upb_arenahas(a) >= size);
|
|
|
|
return upb_arena_malloc(a, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *upb_arena_doalloc(upb_alloc *alloc, void *ptr, size_t oldsize,
|
|
|
|
size_t size) {
|
|
|
|
upb_arena *a = (upb_arena*)alloc; /* upb_alloc is initial member. */
|
|
|
|
return upb_arena_realloc(a, ptr, oldsize, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Public Arena API ***********************************************************/
|
|
|
|
|
|
|
|
upb_arena *arena_initslow(void *mem, size_t n, upb_alloc *alloc) {
|
|
|
|
const size_t first_block_overhead = sizeof(upb_arena) + memblock_reserve;
|
|
|
|
upb_arena *a;
|
|
|
|
|
|
|
|
/* We need to malloc the initial block. */
|
|
|
|
n = first_block_overhead + 256;
|
|
|
|
if (!alloc || !(mem = upb_malloc(alloc, n))) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
a = UPB_PTR_AT(mem, n - sizeof(*a), upb_arena);
|
|
|
|
n -= sizeof(*a);
|
|
|
|
|
|
|
|
a->head.alloc.func = &upb_arena_doalloc;
|
|
|
|
a->block_alloc = alloc;
|
|
|
|
a->parent = a;
|
|
|
|
a->refcount = 1;
|
|
|
|
a->freelist = NULL;
|
|
|
|
a->freelist_tail = NULL;
|
|
|
|
a->cleanup_metadata = upb_cleanup_metadata(NULL, false);
|
|
|
|
|
|
|
|
upb_arena_addblock(a, a, mem, n);
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
upb_arena *upb_arena_init(void *mem, size_t n, upb_alloc *alloc) {
|
|
|
|
upb_arena *a;
|
|
|
|
|
|
|
|
/* Round block size down to alignof(*a) since we will allocate the arena
|
|
|
|
* itself at the end. */
|
|
|
|
n = UPB_ALIGN_DOWN(n, UPB_ALIGN_OF(upb_arena));
|
|
|
|
|
|
|
|
if (UPB_UNLIKELY(n < sizeof(upb_arena))) {
|
|
|
|
return arena_initslow(mem, n, alloc);
|
|
|
|
}
|
|
|
|
|
|
|
|
a = UPB_PTR_AT(mem, n - sizeof(*a), upb_arena);
|
|
|
|
|
|
|
|
a->head.alloc.func = &upb_arena_doalloc;
|
|
|
|
a->block_alloc = alloc;
|
|
|
|
a->parent = a;
|
|
|
|
a->refcount = 1;
|
|
|
|
a->last_size = UPB_MAX(128, n);
|
|
|
|
a->head.ptr = mem;
|
|
|
|
a->head.end = UPB_PTR_AT(mem, n - sizeof(*a), char);
|
|
|
|
a->freelist = NULL;
|
|
|
|
a->cleanup_metadata = upb_cleanup_metadata(NULL, true);
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void arena_dofree(upb_arena *a) {
|
|
|
|
mem_block *block = a->freelist;
|
|
|
|
UPB_ASSERT(a->parent == a);
|
|
|
|
UPB_ASSERT(a->refcount == 0);
|
|
|
|
|
|
|
|
while (block) {
|
|
|
|
/* Load first since we are deleting block. */
|
|
|
|
mem_block *next = block->next;
|
|
|
|
|
|
|
|
if (block->cleanups > 0) {
|
|
|
|
cleanup_ent *end = UPB_PTR_AT(block, block->size, void);
|
|
|
|
cleanup_ent *ptr = end - block->cleanups;
|
|
|
|
|
|
|
|
for (; ptr < end; ptr++) {
|
|
|
|
ptr->cleanup(ptr->ud);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
upb_free(a->block_alloc, block);
|
|
|
|
block = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void upb_arena_free(upb_arena *a) {
|
|
|
|
a = arena_findroot(a);
|
|
|
|
if (--a->refcount == 0) arena_dofree(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool upb_arena_addcleanup(upb_arena *a, void *ud, upb_cleanup_func *func) {
|
|
|
|
cleanup_ent *ent;
|
|
|
|
uint32_t* cleanups = upb_cleanup_pointer(a->cleanup_metadata);
|
|
|
|
|
|
|
|
if (!cleanups || _upb_arenahas(a) < sizeof(cleanup_ent)) {
|
|
|
|
if (!upb_arena_allocblock(a, 128)) return false; /* Out of memory. */
|
|
|
|
UPB_ASSERT(_upb_arenahas(a) >= sizeof(cleanup_ent));
|
|
|
|
cleanups = upb_cleanup_pointer(a->cleanup_metadata);
|
|
|
|
}
|
|
|
|
|
|
|
|
a->head.end -= sizeof(cleanup_ent);
|
|
|
|
ent = (cleanup_ent*)a->head.end;
|
|
|
|
(*cleanups)++;
|
|
|
|
UPB_UNPOISON_MEMORY_REGION(ent, sizeof(cleanup_ent));
|
|
|
|
|
|
|
|
ent->cleanup = func;
|
|
|
|
ent->ud = ud;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool upb_arena_fuse(upb_arena *a1, upb_arena *a2) {
|
|
|
|
upb_arena *r1 = arena_findroot(a1);
|
|
|
|
upb_arena *r2 = arena_findroot(a2);
|
|
|
|
|
|
|
|
if (r1 == r2) return true; /* Already fused. */
|
|
|
|
|
|
|
|
/* Do not fuse initial blocks since we cannot lifetime extend them. */
|
|
|
|
if (upb_cleanup_has_initial_block(r1->cleanup_metadata)) return false;
|
|
|
|
if (upb_cleanup_has_initial_block(r2->cleanup_metadata)) return false;
|
|
|
|
|
|
|
|
/* Only allow fuse with a common allocator */
|
|
|
|
if (r1->block_alloc != r2->block_alloc) return false;
|
|
|
|
|
|
|
|
/* We want to join the smaller tree to the larger tree.
|
|
|
|
* So swap first if they are backwards. */
|
|
|
|
if (r1->refcount < r2->refcount) {
|
|
|
|
upb_arena *tmp = r1;
|
|
|
|
r1 = r2;
|
|
|
|
r2 = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* r1 takes over r2's freelist and refcount. */
|
|
|
|
r1->refcount += r2->refcount;
|
|
|
|
if (r2->freelist_tail) {
|
|
|
|
UPB_ASSERT(r2->freelist_tail->next == NULL);
|
|
|
|
r2->freelist_tail->next = r1->freelist;
|
|
|
|
r1->freelist = r2->freelist;
|
|
|
|
}
|
|
|
|
r2->parent = r1;
|
|
|
|
return true;
|
|
|
|
}
|