Split upb::Arena/upb::Allocator from upb::Environment. (#58)
* Split upb::Arena/upb::Allocator from upb::Environment. This will allow arenas and allocators to be used independently of environments, which will be important for an upcoming change (a message representation). Overall this design feels cleaner that the previous Environment/SeededAllocator design. As part of this change, moved all allocations in upb to use a global allocator instead of hard-coding malloc/free. This will allow injecting OOM faults for more robust testing. One place that doesn't use the global allocator is the tracked ref code. Instead of its previous approach of CHECK_OOM() after every malloc() or table insert, it simply uses an allocator that does this automatically. I moved Allocator/Arena/Environment into upb.h. This seems principled since these are the only types in upb whose size is directly exposed to users, since they form the basis of memory allocation strategy. * Cleaned up some header includes and fixed more malloc -> upb_gmalloc(). * Changes from PR review. * Don't use UINTPTR_MAX or UINT64_MAX. * Punt on adding line/file for now. * We actually can't store (uint64_t)-1, update comment and test.pull/13171/head
parent
04786dc2b3
commit
68bc62a7fa
35 changed files with 1593 additions and 1098 deletions
@ -1,273 +0,0 @@ |
||||
|
||||
#include "upb/env.h" |
||||
|
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
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; |
||||
} |
@ -1,262 +0,0 @@ |
||||
/*
|
||||
** upb::Environment (upb_env) |
||||
** |
||||
** A upb::Environment provides a means for injecting malloc and an |
||||
** error-reporting callback into encoders/decoders. This allows them to be |
||||
** independent of nearly all assumptions about their actual environment. |
||||
** |
||||
** It is also a container for allocating the encoders/decoders themselves that |
||||
** insulates clients from knowing their actual size. This provides ABI |
||||
** compatibility even if the size of the objects change. And this allows the |
||||
** structure definitions to be in the .c files instead of the .h files, making |
||||
** the .h files smaller and more readable. |
||||
*/ |
||||
|
||||
#include "upb/upb.h" |
||||
|
||||
#ifndef UPB_ENV_H_ |
||||
#define UPB_ENV_H_ |
||||
|
||||
#ifdef __cplusplus |
||||
namespace upb { |
||||
class Environment; |
||||
class SeededAllocator; |
||||
} |
||||
#endif |
||||
|
||||
UPB_DECLARE_TYPE(upb::Environment, upb_env) |
||||
UPB_DECLARE_TYPE(upb::SeededAllocator, upb_seededalloc) |
||||
|
||||
typedef void *upb_alloc_func(void *ud, void *ptr, size_t oldsize, size_t size); |
||||
typedef void upb_cleanup_func(void *ud); |
||||
typedef bool upb_error_func(void *ud, const upb_status *status); |
||||
|
||||
#ifdef __cplusplus |
||||
|
||||
/* An environment is *not* thread-safe. */ |
||||
class upb::Environment { |
||||
public: |
||||
Environment(); |
||||
~Environment(); |
||||
|
||||
/* Set a custom memory allocation function for the environment. May ONLY
|
||||
* be called before any calls to Malloc()/Realloc()/AddCleanup() below. |
||||
* If this is not called, the system realloc() function will be used. |
||||
* The given user pointer "ud" will be passed to the allocation function. |
||||
* |
||||
* The allocation function will not receive corresponding "free" calls. it |
||||
* must ensure that the memory is valid for the lifetime of the Environment, |
||||
* but it may be reclaimed any time thereafter. The likely usage is that |
||||
* "ud" points to a stateful allocator, and that the allocator frees all |
||||
* memory, arena-style, when it is destroyed. In this case the allocator must |
||||
* outlive the Environment. Another possibility is that the allocation |
||||
* function returns GC-able memory that is guaranteed to be GC-rooted for the |
||||
* life of the Environment. */ |
||||
void SetAllocationFunction(upb_alloc_func* alloc, void* ud); |
||||
|
||||
template<class T> |
||||
void SetAllocator(T* allocator) { |
||||
SetAllocationFunction(allocator->GetAllocationFunction(), allocator); |
||||
} |
||||
|
||||
/* Set a custom error reporting function. */ |
||||
void SetErrorFunction(upb_error_func* func, void* ud); |
||||
|
||||
/* Set the error reporting function to simply copy the status to the given
|
||||
* status and abort. */ |
||||
void ReportErrorsTo(Status* status); |
||||
|
||||
/* Returns true if all allocations and AddCleanup() calls have succeeded,
|
||||
* and no errors were reported with ReportError() (except ones that recovered |
||||
* successfully). */ |
||||
bool ok() const; |
||||
|
||||
/* Functions for use by encoders/decoders. **********************************/ |
||||
|
||||
/* Reports an error to this environment's callback, returning true if
|
||||
* the caller should try to recover. */ |
||||
bool ReportError(const Status* status); |
||||
|
||||
/* Allocate memory. Uses the environment's allocation function.
|
||||
* |
||||
* There is no need to free(). All memory will be freed automatically, but is |
||||
* guaranteed to outlive the Environment. */ |
||||
void* Malloc(size_t size); |
||||
|
||||
/* Reallocate memory. Preserves "oldsize" bytes from the existing buffer
|
||||
* Requires: oldsize <= existing_size. |
||||
* |
||||
* TODO(haberman): should we also enforce that oldsize <= size? */ |
||||
void* Realloc(void* ptr, size_t oldsize, size_t size); |
||||
|
||||
/* Add a cleanup function to run when the environment is destroyed.
|
||||
* Returns false on out-of-memory. |
||||
* |
||||
* The first call to AddCleanup() after SetAllocationFunction() is guaranteed |
||||
* to return true -- this makes it possible to robustly set a cleanup handler |
||||
* for a custom allocation function. */ |
||||
bool AddCleanup(upb_cleanup_func* func, void* ud); |
||||
|
||||
/* Total number of bytes that have been allocated. It is undefined what
|
||||
* Realloc() does to this counter. */ |
||||
size_t BytesAllocated() const; |
||||
|
||||
private: |
||||
UPB_DISALLOW_COPY_AND_ASSIGN(Environment) |
||||
|
||||
#else |
||||
struct upb_env { |
||||
#endif /* __cplusplus */ |
||||
|
||||
bool ok_; |
||||
size_t bytes_allocated; |
||||
|
||||
/* Alloc function. */ |
||||
upb_alloc_func *alloc; |
||||
void *alloc_ud; |
||||
|
||||
/* Error-reporting function. */ |
||||
upb_error_func *err; |
||||
void *err_ud; |
||||
|
||||
/* Userdata for default alloc func. */ |
||||
void *default_alloc_ud; |
||||
|
||||
/* Cleanup entries. Pointer to a cleanup_ent, defined in env.c */ |
||||
void *cleanup_head; |
||||
|
||||
/* For future expansion, since the size of this struct is exposed to users. */ |
||||
void *future1; |
||||
void *future2; |
||||
}; |
||||
|
||||
UPB_BEGIN_EXTERN_C |
||||
|
||||
void upb_env_init(upb_env *e); |
||||
void upb_env_uninit(upb_env *e); |
||||
void upb_env_setallocfunc(upb_env *e, upb_alloc_func *func, void *ud); |
||||
void upb_env_seterrorfunc(upb_env *e, upb_error_func *func, void *ud); |
||||
void upb_env_reporterrorsto(upb_env *e, upb_status *status); |
||||
bool upb_env_ok(const upb_env *e); |
||||
bool upb_env_reporterror(upb_env *e, const upb_status *status); |
||||
void *upb_env_malloc(upb_env *e, size_t size); |
||||
void *upb_env_realloc(upb_env *e, void *ptr, size_t oldsize, size_t size); |
||||
bool upb_env_addcleanup(upb_env *e, upb_cleanup_func *func, void *ud); |
||||
size_t upb_env_bytesallocated(const upb_env *e); |
||||
|
||||
UPB_END_EXTERN_C |
||||
|
||||
#ifdef __cplusplus |
||||
|
||||
/* An allocator that allocates from an initial memory region (likely the stack)
|
||||
* before falling back to another allocator. */ |
||||
class upb::SeededAllocator { |
||||
public: |
||||
SeededAllocator(void *mem, size_t len); |
||||
~SeededAllocator(); |
||||
|
||||
/* Set a custom fallback memory allocation function for the allocator, to use
|
||||
* once the initial region runs out. |
||||
* |
||||
* May ONLY be called before GetAllocationFunction(). If this is not |
||||
* called, the system realloc() will be the fallback allocator. */ |
||||
void SetFallbackAllocator(upb_alloc_func *alloc, void *ud); |
||||
|
||||
/* Gets the allocation function for this allocator. */ |
||||
upb_alloc_func* GetAllocationFunction(); |
||||
|
||||
private: |
||||
UPB_DISALLOW_COPY_AND_ASSIGN(SeededAllocator) |
||||
|
||||
#else |
||||
struct upb_seededalloc { |
||||
#endif /* __cplusplus */ |
||||
|
||||
/* Fallback alloc function. */ |
||||
upb_alloc_func *alloc; |
||||
upb_cleanup_func *alloc_cleanup; |
||||
void *alloc_ud; |
||||
bool need_cleanup; |
||||
bool returned_allocfunc; |
||||
|
||||
/* Userdata for default alloc func. */ |
||||
void *default_alloc_ud; |
||||
|
||||
/* Pointers for the initial memory region. */ |
||||
char *mem_base; |
||||
char *mem_ptr; |
||||
char *mem_limit; |
||||
|
||||
/* For future expansion, since the size of this struct is exposed to users. */ |
||||
void *future1; |
||||
void *future2; |
||||
}; |
||||
|
||||
UPB_BEGIN_EXTERN_C |
||||
|
||||
void upb_seededalloc_init(upb_seededalloc *a, void *mem, size_t len); |
||||
void upb_seededalloc_uninit(upb_seededalloc *a); |
||||
void upb_seededalloc_setfallbackalloc(upb_seededalloc *a, upb_alloc_func *func, |
||||
void *ud); |
||||
upb_alloc_func *upb_seededalloc_getallocfunc(upb_seededalloc *a); |
||||
|
||||
UPB_END_EXTERN_C |
||||
|
||||
#ifdef __cplusplus |
||||
|
||||
namespace upb { |
||||
|
||||
inline Environment::Environment() { |
||||
upb_env_init(this); |
||||
} |
||||
inline Environment::~Environment() { |
||||
upb_env_uninit(this); |
||||
} |
||||
inline void Environment::SetAllocationFunction(upb_alloc_func *alloc, |
||||
void *ud) { |
||||
upb_env_setallocfunc(this, alloc, ud); |
||||
} |
||||
inline void Environment::SetErrorFunction(upb_error_func *func, void *ud) { |
||||
upb_env_seterrorfunc(this, func, ud); |
||||
} |
||||
inline void Environment::ReportErrorsTo(Status* status) { |
||||
upb_env_reporterrorsto(this, status); |
||||
} |
||||
inline bool Environment::ok() const { |
||||
return upb_env_ok(this); |
||||
} |
||||
inline bool Environment::ReportError(const Status* status) { |
||||
return upb_env_reporterror(this, status); |
||||
} |
||||
inline void *Environment::Malloc(size_t size) { |
||||
return upb_env_malloc(this, size); |
||||
} |
||||
inline void *Environment::Realloc(void *ptr, size_t oldsize, size_t size) { |
||||
return upb_env_realloc(this, ptr, oldsize, size); |
||||
} |
||||
inline bool Environment::AddCleanup(upb_cleanup_func *func, void *ud) { |
||||
return upb_env_addcleanup(this, func, ud); |
||||
} |
||||
inline size_t Environment::BytesAllocated() const { |
||||
return upb_env_bytesallocated(this); |
||||
} |
||||
|
||||
inline SeededAllocator::SeededAllocator(void *mem, size_t len) { |
||||
upb_seededalloc_init(this, mem, len); |
||||
} |
||||
inline SeededAllocator::~SeededAllocator() { |
||||
upb_seededalloc_uninit(this); |
||||
} |
||||
inline void SeededAllocator::SetFallbackAllocator(upb_alloc_func *alloc, |
||||
void *ud) { |
||||
upb_seededalloc_setfallbackalloc(this, alloc, ud); |
||||
} |
||||
inline upb_alloc_func *SeededAllocator::GetAllocationFunction() { |
||||
return upb_seededalloc_getallocfunc(this); |
||||
} |
||||
|
||||
} /* namespace upb */ |
||||
|
||||
#endif /* __cplusplus */ |
||||
|
||||
#endif /* UPB_ENV_H_ */ |
Loading…
Reference in new issue