summaryrefslogtreecommitdiffstats
path: root/src/allocator.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/allocator.h273
1 files changed, 273 insertions, 0 deletions
diff --git a/src/allocator.h b/src/allocator.h
new file mode 100644
index 0000000..97b9a41
--- /dev/null
+++ b/src/allocator.h
@@ -0,0 +1,273 @@
+/*
+ * 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 constexpr 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>
+constexpr 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