nghttp2/src/allocator.h

274 lines
9.3 KiB
C++

/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include "nghttp2_config.h"
#ifndef _WIN32
# include <sys/uio.h>
#endif // !_WIN32
#include <cassert>
#include <utility>
#include "template.h"
namespace nghttp2 {
struct MemBlock {
// The next MemBlock to chain them. This is for book keeping
// purpose to free them later.
MemBlock *next;
// begin is the pointer to the beginning of buffer. last is the
// location of next write. end is the one beyond of the end of the
// buffer.
uint8_t *begin, *last, *end;
};
// BlockAllocator allocates memory block with given size at once, and
// cuts the region from it when allocation is requested. If the
// requested size is larger than given threshold (plus small internal
// overhead), it will be allocated in a distinct buffer on demand.
// The |isolation_threshold| must be less than or equal to
// |block_size|.
struct BlockAllocator {
BlockAllocator(size_t block_size, size_t isolation_threshold)
: retain(nullptr),
head(nullptr),
block_size(block_size),
isolation_threshold(std::min(block_size, isolation_threshold)) {
assert(isolation_threshold <= block_size);
}
~BlockAllocator() { reset(); }
BlockAllocator(BlockAllocator &&other) noexcept
: retain{std::exchange(other.retain, nullptr)},
head{std::exchange(other.head, nullptr)},
block_size(other.block_size),
isolation_threshold(other.isolation_threshold) {}
BlockAllocator &operator=(BlockAllocator &&other) noexcept {
reset();
retain = std::exchange(other.retain, nullptr);
head = std::exchange(other.head, nullptr);
block_size = other.block_size;
isolation_threshold = other.isolation_threshold;
return *this;
}
BlockAllocator(const BlockAllocator &) = delete;
BlockAllocator &operator=(const BlockAllocator &) = delete;
void reset() {
for (auto mb = retain; mb;) {
auto next = mb->next;
delete[] reinterpret_cast<uint8_t *>(mb);
mb = next;
}
retain = nullptr;
head = nullptr;
}
MemBlock *alloc_mem_block(size_t size) {
auto block = new uint8_t[sizeof(MemBlock) + size];
auto mb = reinterpret_cast<MemBlock *>(block);
mb->next = retain;
mb->begin = mb->last = block + sizeof(MemBlock);
mb->end = mb->begin + size;
retain = mb;
return mb;
}
void *alloc(size_t size) {
if (size + sizeof(size_t) >= isolation_threshold) {
auto len = std::max(static_cast<size_t>(16), size);
// We will store the allocated size in size_t field.
auto mb = alloc_mem_block(len + sizeof(size_t));
auto sp = reinterpret_cast<size_t *>(mb->begin);
*sp = len;
mb->last = mb->end;
return mb->begin + sizeof(size_t);
}
if (!head ||
head->end - head->last < static_cast<ssize_t>(size + sizeof(size_t))) {
head = alloc_mem_block(block_size);
}
// We will store the allocated size in size_t field.
auto res = head->last + sizeof(size_t);
auto sp = reinterpret_cast<size_t *>(head->last);
*sp = size;
head->last = reinterpret_cast<uint8_t *>(
(reinterpret_cast<intptr_t>(res + size) + 0xf) & ~0xf);
return res;
}
// Returns allocated size for memory pointed by |ptr|. We assume
// that |ptr| was returned from alloc() or realloc().
size_t get_alloc_length(void *ptr) {
return *reinterpret_cast<size_t *>(static_cast<uint8_t *>(ptr) -
sizeof(size_t));
}
// Allocates memory of at least |size| bytes. If |ptr| is nullptr,
// this is equivalent to alloc(size). If |ptr| is not nullptr,
// obtain the allocated size for |ptr|, assuming that |ptr| was
// returned from alloc() or realloc(). If the allocated size is
// greater than or equal to size, |ptr| is returned. Otherwise,
// allocates at least |size| bytes of memory, and the original
// content pointed by |ptr| is copied to the newly allocated memory.
void *realloc(void *ptr, size_t size) {
if (!ptr) {
return alloc(size);
}
auto alloclen = get_alloc_length(ptr);
auto p = reinterpret_cast<uint8_t *>(ptr);
if (size <= alloclen) {
return ptr;
}
auto nalloclen = std::max(size + 1, alloclen * 2);
auto res = alloc(nalloclen);
std::copy_n(p, alloclen, static_cast<uint8_t *>(res));
return res;
}
// This holds live memory block to free them in dtor.
MemBlock *retain;
// Current memory block to use.
MemBlock *head;
// size of single memory block
size_t block_size;
// if allocation greater or equal to isolation_threshold bytes is
// requested, allocate dedicated block.
size_t isolation_threshold;
};
// Makes a copy of |src|. The resulting string will be
// NULL-terminated.
template <typename BlockAllocator>
StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
auto p = dst;
p = std::copy(std::begin(src), std::end(src), p);
*p = '\0';
return StringRef{dst, src.size()};
}
// private function used in concat_string_ref. this is the base
// function of concat_string_ref_count().
inline size_t concat_string_ref_count(size_t acc) { return acc; }
// private function used in concat_string_ref. This function counts
// the sum of length of given arguments. The calculated length is
// accumulated, and passed to the next function.
template <typename... Args>
size_t concat_string_ref_count(size_t acc, const StringRef &value,
Args &&... args) {
return concat_string_ref_count(acc + value.size(),
std::forward<Args>(args)...);
}
// private function used in concat_string_ref. this is the base
// function of concat_string_ref_copy().
inline uint8_t *concat_string_ref_copy(uint8_t *p) { return p; }
// private function used in concat_string_ref. This function copies
// given strings into |p|. |p| is incremented by the copied length,
// and returned. In the end, return value points to the location one
// beyond the last byte written.
template <typename... Args>
uint8_t *concat_string_ref_copy(uint8_t *p, const StringRef &value,
Args &&... args) {
p = std::copy(std::begin(value), std::end(value), p);
return concat_string_ref_copy(p, std::forward<Args>(args)...);
}
// Returns the string which is the concatenation of |args| in the
// given order. The resulting string will be NULL-terminated.
template <typename BlockAllocator, typename... Args>
StringRef concat_string_ref(BlockAllocator &alloc, Args &&... args) {
size_t len = concat_string_ref_count(0, std::forward<Args>(args)...);
auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
auto p = dst;
p = concat_string_ref_copy(p, std::forward<Args>(args)...);
*p = '\0';
return StringRef{dst, len};
}
// Returns the string which is the concatenation of |value| and |args|
// in the given order. The resulting string will be NULL-terminated.
// This function assumes that the pointer value value.c_str() was
// obtained from alloc.alloc() or alloc.realloc(), and attempts to use
// unused memory region by using alloc.realloc(). If value is empty,
// then just call concat_string_ref().
template <typename BlockAllocator, typename... Args>
StringRef realloc_concat_string_ref(BlockAllocator &alloc,
const StringRef &value, Args &&... args) {
if (value.empty()) {
return concat_string_ref(alloc, std::forward<Args>(args)...);
}
auto len =
value.size() + concat_string_ref_count(0, std::forward<Args>(args)...);
auto dst = static_cast<uint8_t *>(
alloc.realloc(const_cast<uint8_t *>(value.byte()), len + 1));
auto p = dst + value.size();
p = concat_string_ref_copy(p, std::forward<Args>(args)...);
*p = '\0';
return StringRef{dst, len};
}
struct ByteRef {
// The pointer to the beginning of the buffer.
uint8_t *base;
// The length of the buffer.
size_t len;
};
// Makes a buffer with given size. The resulting byte string might
// not be NULL-terminated.
template <typename BlockAllocator>
ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) {
auto dst = static_cast<uint8_t *>(alloc.alloc(size));
return {dst, size};
}
} // namespace nghttp2
#endif // ALLOCATOR_H