• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2016 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #ifndef ALLOCATOR_H
26 #define ALLOCATOR_H
27 
28 #include "nghttp2_config.h"
29 
30 #ifndef _WIN32
31 #  include <sys/uio.h>
32 #endif // !_WIN32
33 
34 #include <cassert>
35 #include <utility>
36 #include <span>
37 
38 #include "template.h"
39 
40 namespace nghttp2 {
41 
42 struct MemBlock {
43   // The next MemBlock to chain them.  This is for book keeping
44   // purpose to free them later.
45   MemBlock *next;
46   // begin is the pointer to the beginning of buffer.  last is the
47   // location of next write.  end is the one beyond of the end of the
48   // buffer.
49   uint8_t *begin, *last, *end;
50 };
51 
52 static_assert((sizeof(MemBlock) & 0xf) == 0);
53 
54 struct ChunkHead {
55   union {
56     size_t size;
57     uint64_t pad1;
58   };
59   uint64_t pad2;
60 };
61 
62 static_assert(sizeof(ChunkHead) == 16);
63 
64 // BlockAllocator allocates memory block with given size at once, and
65 // cuts the region from it when allocation is requested.  If the
66 // requested size is larger than given threshold (plus small internal
67 // overhead), it will be allocated in a distinct buffer on demand.
68 // The |isolation_threshold| must be less than or equal to
69 // |block_size|.
70 struct BlockAllocator {
BlockAllocatorBlockAllocator71   BlockAllocator(size_t block_size, size_t isolation_threshold)
72       : retain(nullptr),
73         head(nullptr),
74         block_size(block_size),
75         isolation_threshold(std::min(block_size, isolation_threshold)) {
76     assert(isolation_threshold <= block_size);
77   }
78 
~BlockAllocatorBlockAllocator79   ~BlockAllocator() { reset(); }
80 
BlockAllocatorBlockAllocator81   BlockAllocator(BlockAllocator &&other) noexcept
82       : retain{std::exchange(other.retain, nullptr)},
83         head{std::exchange(other.head, nullptr)},
84         block_size(other.block_size),
85         isolation_threshold(other.isolation_threshold) {}
86 
87   BlockAllocator &operator=(BlockAllocator &&other) noexcept {
88     reset();
89 
90     retain = std::exchange(other.retain, nullptr);
91     head = std::exchange(other.head, nullptr);
92     block_size = other.block_size;
93     isolation_threshold = other.isolation_threshold;
94 
95     return *this;
96   }
97 
98   BlockAllocator(const BlockAllocator &) = delete;
99   BlockAllocator &operator=(const BlockAllocator &) = delete;
100 
resetBlockAllocator101   void reset() {
102     for (auto mb = retain; mb;) {
103       auto next = mb->next;
104       operator delete[](reinterpret_cast<uint8_t *>(mb), std::align_val_t(16));
105       mb = next;
106     }
107 
108     retain = nullptr;
109     head = nullptr;
110   }
111 
alloc_mem_blockBlockAllocator112   MemBlock *alloc_mem_block(size_t size) {
113     auto block = new (std::align_val_t(16)) uint8_t[sizeof(MemBlock) + size];
114     auto mb = reinterpret_cast<MemBlock *>(block);
115 
116     mb->next = retain;
117     mb->begin = mb->last = reinterpret_cast<uint8_t *>(
118         (reinterpret_cast<intptr_t>(block + sizeof(MemBlock)) + 0xf) & ~0xf);
119     mb->end = mb->begin + size;
120     retain = mb;
121     return mb;
122   }
123 
alloc_unitBlockAllocator124   constexpr size_t alloc_unit(size_t size) { return sizeof(ChunkHead) + size; }
125 
allocBlockAllocator126   void *alloc(size_t size) {
127     auto au = alloc_unit(size);
128 
129     if (au >= isolation_threshold) {
130       size = std::max(static_cast<size_t>(16), size);
131       // We will store the allocated size in size_t field.
132       auto mb = alloc_mem_block(alloc_unit(size));
133       auto ch = reinterpret_cast<ChunkHead *>(mb->begin);
134       ch->size = size;
135       mb->last = mb->end;
136       return mb->begin + sizeof(ChunkHead);
137     }
138 
139     if (!head || static_cast<size_t>(head->end - head->last) < au) {
140       head = alloc_mem_block(block_size);
141     }
142 
143     // We will store the allocated size in size_t field.
144     auto res = head->last + sizeof(ChunkHead);
145     auto ch = reinterpret_cast<ChunkHead *>(head->last);
146     ch->size = size;
147 
148     head->last = reinterpret_cast<uint8_t *>(
149         (reinterpret_cast<intptr_t>(res + size) + 0xf) & ~0xf);
150 
151     return res;
152   }
153 
154   // Returns allocated size for memory pointed by |ptr|.  We assume
155   // that |ptr| was returned from alloc() or realloc().
get_alloc_lengthBlockAllocator156   size_t get_alloc_length(void *ptr) {
157     return reinterpret_cast<ChunkHead *>(static_cast<uint8_t *>(ptr) -
158                                          sizeof(ChunkHead))
159         ->size;
160   }
161 
162   // Allocates memory of at least |size| bytes.  If |ptr| is nullptr,
163   // this is equivalent to alloc(size).  If |ptr| is not nullptr,
164   // obtain the allocated size for |ptr|, assuming that |ptr| was
165   // returned from alloc() or realloc().  If the allocated size is
166   // greater than or equal to size, |ptr| is returned.  Otherwise,
167   // allocates at least |size| bytes of memory, and the original
168   // content pointed by |ptr| is copied to the newly allocated memory.
reallocBlockAllocator169   void *realloc(void *ptr, size_t size) {
170     if (!ptr) {
171       return alloc(size);
172     }
173     auto alloclen = get_alloc_length(ptr);
174     auto p = reinterpret_cast<uint8_t *>(ptr);
175     if (size <= alloclen) {
176       return ptr;
177     }
178 
179     auto nalloclen = std::max(size + 1, alloclen * 2);
180 
181     auto res = alloc(nalloclen);
182     std::copy_n(p, alloclen, static_cast<uint8_t *>(res));
183 
184     return res;
185   }
186 
187   // This holds live memory block to free them in dtor.
188   MemBlock *retain;
189   // Current memory block to use.
190   MemBlock *head;
191   // size of single memory block
192   size_t block_size;
193   // if allocation greater or equal to isolation_threshold bytes is
194   // requested, allocate dedicated block.
195   size_t isolation_threshold;
196 };
197 
198 // Makes a copy of |src|.  The resulting string will be
199 // NULL-terminated.
200 template <typename BlockAllocator>
make_string_ref(BlockAllocator & alloc,const StringRef & src)201 StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
202   auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
203   auto p = dst;
204   p = std::copy(std::begin(src), std::end(src), p);
205   *p = '\0';
206   return StringRef{dst, src.size()};
207 }
208 
209 // private function used in concat_string_ref.  this is the base
210 // function of concat_string_ref_count().
concat_string_ref_count(size_t acc)211 inline constexpr size_t concat_string_ref_count(size_t acc) { return acc; }
212 
213 // private function used in concat_string_ref.  This function counts
214 // the sum of length of given arguments.  The calculated length is
215 // accumulated, and passed to the next function.
216 template <typename... Args>
concat_string_ref_count(size_t acc,const StringRef & value,Args &&...args)217 constexpr size_t concat_string_ref_count(size_t acc, const StringRef &value,
218                                          Args &&...args) {
219   return concat_string_ref_count(acc + value.size(),
220                                  std::forward<Args>(args)...);
221 }
222 
223 // private function used in concat_string_ref.  this is the base
224 // function of concat_string_ref_copy().
concat_string_ref_copy(uint8_t * p)225 inline uint8_t *concat_string_ref_copy(uint8_t *p) { return p; }
226 
227 // private function used in concat_string_ref.  This function copies
228 // given strings into |p|.  |p| is incremented by the copied length,
229 // and returned.  In the end, return value points to the location one
230 // beyond the last byte written.
231 template <typename... Args>
concat_string_ref_copy(uint8_t * p,const StringRef & value,Args &&...args)232 uint8_t *concat_string_ref_copy(uint8_t *p, const StringRef &value,
233                                 Args &&...args) {
234   p = std::copy(std::begin(value), std::end(value), p);
235   return concat_string_ref_copy(p, std::forward<Args>(args)...);
236 }
237 
238 // Returns the string which is the concatenation of |args| in the
239 // given order.  The resulting string will be NULL-terminated.
240 template <typename BlockAllocator, typename... Args>
concat_string_ref(BlockAllocator & alloc,Args &&...args)241 StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) {
242   size_t len = concat_string_ref_count(0, std::forward<Args>(args)...);
243   auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
244   auto p = dst;
245   p = concat_string_ref_copy(p, std::forward<Args>(args)...);
246   *p = '\0';
247   return StringRef{dst, len};
248 }
249 
250 // Returns the string which is the concatenation of |value| and |args|
251 // in the given order.  The resulting string will be NULL-terminated.
252 // This function assumes that the pointer value value.c_str() was
253 // obtained from alloc.alloc() or alloc.realloc(), and attempts to use
254 // unused memory region by using alloc.realloc().  If value is empty,
255 // then just call concat_string_ref().
256 template <typename BlockAllocator, typename... Args>
realloc_concat_string_ref(BlockAllocator & alloc,const StringRef & value,Args &&...args)257 StringRef realloc_concat_string_ref(BlockAllocator &alloc,
258                                     const StringRef &value, Args &&...args) {
259   if (value.empty()) {
260     return concat_string_ref(alloc, std::forward<Args>(args)...);
261   }
262 
263   auto len =
264       value.size() + concat_string_ref_count(0, std::forward<Args>(args)...);
265   auto dst = static_cast<uint8_t *>(
266       alloc.realloc(const_cast<uint8_t *>(value.byte()), len + 1));
267   auto p = dst + value.size();
268   p = concat_string_ref_copy(p, std::forward<Args>(args)...);
269   *p = '\0';
270 
271   return StringRef{dst, len};
272 }
273 
274 // Makes an uninitialized buffer with given size.
275 template <typename BlockAllocator>
make_byte_ref(BlockAllocator & alloc,size_t size)276 std::span<uint8_t> make_byte_ref(BlockAllocator &alloc, size_t size) {
277   return {static_cast<uint8_t *>(alloc.alloc(size)), size};
278 }
279 
280 } // namespace nghttp2
281 
282 #endif // ALLOCATOR_H
283