/* * 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 #endif // !_WIN32 #include #include #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(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(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(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(mb->begin); *sp = len; mb->last = mb->end; return mb->begin + sizeof(size_t); } if (!head || head->end - head->last < static_cast(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(head->last); *sp = size; head->last = reinterpret_cast( (reinterpret_cast(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(static_cast(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(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(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 StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) { auto dst = static_cast(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 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)...); } // 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 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)...); } // Returns the string which is the concatenation of |args| in the // given order. The resulting string will be NULL-terminated. template StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) { size_t len = concat_string_ref_count(0, std::forward(args)...); auto dst = static_cast(alloc.alloc(len + 1)); auto p = dst; p = concat_string_ref_copy(p, std::forward(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 StringRef realloc_concat_string_ref(BlockAllocator &alloc, const StringRef &value, Args &&...args) { if (value.empty()) { return concat_string_ref(alloc, std::forward(args)...); } auto len = value.size() + concat_string_ref_count(0, std::forward(args)...); auto dst = static_cast( alloc.realloc(const_cast(value.byte()), len + 1)); auto p = dst + value.size(); p = concat_string_ref_copy(p, std::forward(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 ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) { auto dst = static_cast(alloc.alloc(size)); return {dst, size}; } } // namespace nghttp2 #endif // ALLOCATOR_H