Merge pull request #18 from haberman/google-internal
Sync from Google-internal development.pull/13171/head
commit
ccc0fd0dbb
33 changed files with 1490 additions and 791 deletions
@ -0,0 +1,261 @@ |
||||
/*
|
||||
* upb - a minimalist implementation of protocol buffers. |
||||
* |
||||
* Copyright (c) 2014 Google Inc. See LICENSE for details. |
||||
* Author: Josh Haberman <jhaberman@gmail.com> |
||||
*/ |
||||
|
||||
#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 |
||||
char data[]; |
||||
} mem_block; |
||||
|
||||
typedef struct { |
||||
mem_block *head; |
||||
} default_alloc_ud; |
||||
|
||||
static void *default_alloc(void *_ud, void *ptr, size_t oldsize, size_t size) { |
||||
UPB_UNUSED(oldsize); |
||||
default_alloc_ud *ud = _ud; |
||||
|
||||
mem_block *from = ptr ? (void*)((char*)ptr - sizeof(mem_block)) : NULL; |
||||
|
||||
#ifndef NDEBUG |
||||
if (from) { |
||||
assert(oldsize <= from->size); |
||||
} |
||||
#endif |
||||
|
||||
mem_block *block = realloc(from, size + sizeof(mem_block)); |
||||
if (!block) return NULL; |
||||
|
||||
#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; |
||||
} |
||||
} 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 &block->data; |
||||
} |
||||
|
||||
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); |
||||
fprintf(stderr, "upb error: %s\n", upb_status_errmsg(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) { |
||||
e->ok_ = true; |
||||
e->bytes_allocated = 0; |
||||
e->cleanup_head = NULL; |
||||
|
||||
default_alloc_ud *ud = (default_alloc_ud*)&e->default_alloc_ud; |
||||
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) { |
||||
assert(oldsize <= size); |
||||
char *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 { |
||||
// Slow path: fallback to other allocator.
|
||||
a->need_cleanup = true; |
||||
return a->alloc(a->alloc_ud, ptr, oldsize, size); |
||||
} |
||||
} |
||||
|
||||
void upb_seededalloc_init(upb_seededalloc *a, void *mem, size_t len) { |
||||
a->mem_base = mem; |
||||
a->mem_ptr = mem; |
||||
a->mem_limit = (char*)mem + len; |
||||
a->need_cleanup = false; |
||||
a->returned_allocfunc = false; |
||||
|
||||
default_alloc_ud *ud = (default_alloc_ud*)&a->default_alloc_ud; |
||||
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; |
||||
} |
@ -0,0 +1,256 @@ |
||||
/*
|
||||
* upb - a minimalist implementation of protocol buffers. |
||||
* |
||||
* Copyright (c) 2014 Google Inc. See LICENSE for details. |
||||
* Author: Josh Haberman <jhaberman@gmail.com> |
||||
* |
||||
* 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); |
||||
|
||||
// An environment is *not* thread-safe.
|
||||
UPB_DEFINE_CLASS0(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); |
||||
, |
||||
UPB_DEFINE_STRUCT0(upb_env, |
||||
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 |
||||
|
||||
// An allocator that allocates from an initial memory region (likely the stack)
|
||||
// before falling back to another allocator.
|
||||
UPB_DEFINE_CLASS0(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); |
||||
, |
||||
UPB_DEFINE_STRUCT0(upb_seededalloc, |
||||
// 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