blob: a95a6456b125fa5963a6a7d7b644aecae2eeb298 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "upb/wire/internal/back_alloc.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "upb/base/internal/log2.h"
#include "upb/mem/internal/arena.h"
// Must be last.
#include "upb/port/def.inc"
char upb_BackAlloc_sentinel;
static size_t upb_BackAlloc_CalcBlockSize(upb_BackAlloc* a, size_t required,
bool* one_off) {
#if UPB_HWASAN
required = UPB_ALIGN_UP(required, UPB_MALLOC_ALIGN);
#endif
size_t organic_block_size =
UPB_PRIVATE(_upb_Arena_NextBlockSize)(a->arena, required, one_off);
// We want to offer amortized linear time, which means we must grow block
// sizes exponentially. However, merely allocating a power of 2 is
// pathological for allocators that use a header with mmap for large
// contiguous allocations. Instead, we want to allocate based on a power of
// 2, but request slightly less to leave room for backing allocator
// metadata. If we had universal size feedback this would not be necessary.
size_t scaled_block_size = upb_RoundUpToPowerOfTwo(required);
// Estimated value such that 128 bytes of possible overhead is not
// significant. 128 bytes should be enough for whatever metadata is needed.
if (scaled_block_size >= 4096 * 4) {
scaled_block_size = upb_RoundUpToPowerOfTwo(required + 128) - 128;
}
// Scaled block size calculations could overflow, but that's OK as it's
// unsigned and won't be used if it's less than the organic block size
if (scaled_block_size > organic_block_size) {
return UPB_PRIVATE(_upb_Arena_NextBlockSize)(a->arena, scaled_block_size,
one_off);
}
return organic_block_size;
}
static char* upb_BackAlloc_Realloc(upb_BackAlloc* a, char* ptr, size_t n) {
size_t copy = a->limit - ptr;
if (SIZE_MAX - copy < n) {
return NULL;
}
bool one_off = false;
size_t required_block_size = copy + n;
size_t size = upb_BackAlloc_CalcBlockSize(a, required_block_size, &one_off);
char* block = UPB_PRIVATE(_upb_Arena_AllocBlock)(a->arena, &size);
if (!block) {
return NULL;
}
UPB_PRIVATE(_upb_Arena_UpdateGrowthState)(a->arena, required_block_size, size,
one_off);
char* dst = block + size - copy;
memcpy(dst, ptr, copy);
if (a->limit != a->buf) {
// Dispose of the old block.
if (a->standalone) {
// Note: while it would technically be possible to give this block to the
// arena to use for allocations, this could lead to a lot of garbage
// blocks that never get used.
UPB_PRIVATE(_upb_Arena_FreeBlock)(a->arena, a->buf);
} else {
UPB_PRIVATE(_upb_Arena_UseBlock)(a->arena, a->buf, a->limit - a->buf);
}
}
a->buf = block;
a->limit = block + size;
a->standalone = true;
return dst - n;
}
char* upb_BackAlloc_Grow(upb_BackAlloc* a, char* ptr, size_t n) {
if (a->limit == a->buf) {
// First allocation: try to steal a block.
size_t size = n;
char* block = UPB_PRIVATE(_upb_Arena_Steal)(a->arena, &size);
if (block) {
UPB_ASSERT(size >= n);
UPB_ASSERT(a->standalone == false);
a->buf = block;
a->limit = block + size;
return a->limit - n;
}
}
return upb_BackAlloc_Realloc(a, ptr, n);
}
#include "upb/port/undef.inc"