Bond
 
Loading...
Searching...
No Matches
output_buffer.h
Go to the documentation of this file.
1// Copyright (c) Microsoft. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
5#pragma once
6
7#include <bond/core/config.h>
8
9#include <bond/core/blob.h>
10#include <bond/core/containers.h>
11#include <bond/core/detail/checked.h>
12#include <bond/core/traits.h>
13#include <boost/static_assert.hpp>
14#include <cstring>
15#include <limits>
16#include <stdexcept>
17
18namespace bond
19{
20
21namespace output_buffer
22{
23
24template <typename T, uint32_t N>
25struct VariableUnsignedUnchecked
26{
27 BOOST_STATIC_ASSERT(N < 10);
28
29 static uint32_t Write(char* p, T value)
30 {
31 uint8_t byte = static_cast<uint8_t>(value);
32
33 if (value >>= 7)
34 {
35 byte |= 0x80;
36 std::memcpy(p + N - 1, &byte, 1);
37 return VariableUnsignedUnchecked<T, N+1>::Write(p, value);
38 }
39 else
40 {
41 std::memcpy(p + N - 1, &byte, 1);
42 return N;
43 }
44 }
45};
46
47
48template <>
49struct VariableUnsignedUnchecked<uint64_t, 10>
50{
51 static uint32_t Write(char* p, uint64_t value)
52 {
53 BOOST_VERIFY(value == 1);
54 const uint8_t byte = 1;
55 std::memcpy(p + 9, &byte, 1);
56 return 10;
57 }
58};
59
60
61template <>
62struct VariableUnsignedUnchecked<uint32_t, 5>
63{
64 static uint32_t Write(char* p, uint32_t value)
65 {
66 const uint8_t byte = static_cast<uint8_t>(value);
67 std::memcpy(p + 4, &byte, 1);
68 return 5;
69 }
70};
71
72
73template <>
74struct VariableUnsignedUnchecked<uint16_t, 3>
75{
76 static uint32_t Write(char* p, uint16_t value)
77 {
78 const uint8_t byte = static_cast<uint8_t>(value);
79 std::memcpy(p + 2, &byte, 1);
80 return 3;
81 }
82};
83
84}
85
87template <typename A = std::allocator<char> >
89{
90public:
92 explicit OutputMemoryStream(const A& allocator = A())
93 : _allocator(allocator),
94 _buffer(),
95 _bufferSize(0),
96 _rangeSize(0),
97 _rangeOffset(0),
98 _minChainningSize(32),
99 _maxChainLength((uint32_t)-1),
100 _rangePtr(0),
101 _blobs(_allocator)
102 {}
103
106 explicit OutputMemoryStream(const boost::shared_ptr<char[]>& buffer,
107 uint32_t size,
108 uint32_t reserveBlobs = 128,
109 const A& allocator = A(),
110 uint32_t minChanningSize = 32,
111 uint32_t maxChainLength = (uint32_t)-1)
112 : _allocator(allocator),
113 _buffer(buffer),
114 _bufferSize(size),
115 _rangeSize(0),
116 _rangeOffset(0),
117 _minChainningSize(minChanningSize),
118 _maxChainLength(maxChainLength),
119 _rangePtr(_buffer.get()),
120 _blobs(allocator)
121 {
122 _blobs.reserve(reserveBlobs);
123 }
124
125
128 explicit OutputMemoryStream(uint32_t reserveSize,
129 uint32_t reserveBlobs = 128,
130 const A& allocator = A(),
131 uint32_t minChanningSize = 32,
132 uint32_t maxChainLength = (uint32_t)-1)
133 : _allocator(allocator),
134 _buffer(boost::allocate_shared_noinit<char[]>(_allocator, reserveSize)),
135 _bufferSize(reserveSize),
136 _rangeSize(0),
137 _rangeOffset(0),
138 _minChainningSize(minChanningSize),
139 _maxChainLength(maxChainLength),
140 _rangePtr(_buffer.get()),
141 _blobs(allocator)
142 {
143 _blobs.reserve(reserveBlobs);
144 }
145
146
149 template <typename T>
150 void GetBuffers(T& buffers) const
151 {
152 buffers.reserve(bond::detail::checked_add(_blobs.size(), 1U));
153
154 //
155 // insert all "ready" blobs
156 //
157 buffers.assign(_blobs.begin(), _blobs.end());
158
159 if (_rangeSize > 0)
160 {
161 //
162 // attach current array, if not empty,
163 // as a last blob
164 //
165 buffers.emplace_back(_buffer, _rangeOffset, _rangeSize);
166 }
167 }
168
173 {
174 //
175 // merge all blobs in the active list with the current one
176 //
177 blob current(_buffer, _rangeOffset, _rangeSize);
178 return merge(_allocator, merge(_allocator, _blobs.begin(), _blobs.end()), current);
179 }
180
181
182 template<typename T>
183 void Write(const T& value)
184 {
185#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
186 //
187 // x86/x64 performance tweak: for small types don't go into generic
188 // Write(), which results in call to memcpy() and additional memory
189 // access. The direct copy of the value (which is likely on register
190 // already) is faster.
191 //
192 if (sizeof(T) + _rangeSize + _rangeOffset <= _bufferSize)
193 {
194 *reinterpret_cast<T*>(_rangePtr + _rangeSize) = value;
195 _rangeSize += sizeof(T);
196 }
197 else
198 {
199 Write(&value, sizeof(value));
200 }
201#else
202 //
203 // We can't use the trick above on platforms that don't support
204 // unaligned memory access, so fall back to the version of Write
205 // that is implemented with memcpy. memcpy handles the alignment for
206 // us.
207 //
208 Write(&value, sizeof(value));
209#endif
210 }
211
212
213 void Write(const void* value, uint32_t size)
214 {
215 uint32_t sizePart = _bufferSize - _rangeSize - _rangeOffset;
216 const char* buffer = static_cast<const char*>(value);
217
218 if (sizePart > size)
219 {
220 sizePart = size;
221 }
222
223 //
224 // Copy to the tail of current range. Note that _rangePtr may still be
225 // null here on initial write to an empty buffer. The behaviour of
226 // std::memcpy() is undefined in that situation, even if size == 0.
227 //
228 if (sizePart > 0)
229 {
230 std::memcpy(_rangePtr + _rangeSize,
231 buffer,
232 sizePart);
233
234 // increase current range size
235 _rangeSize += sizePart;
236 }
237
238 //
239 // if there is more bytes to copy, allocate a new buffer
240 //
241 if (size != sizePart)
242 {
243 BOOST_ASSERT(_bufferSize == _rangeSize + _rangeOffset);
244
245 //
246 // shift input data window by size of copied bytes, if any
247 //
248 size -= sizePart;
249 buffer += sizePart;
250
251 //
252 // snap current range to internal list of blobs, if not empty
253 //
254 if (_rangeSize > 0)
255 {
256 _blobs.emplace_back(_buffer, _rangeOffset, _rangeSize);
257 }
258
259 // cap buffer to prevent overflow
260 if (_bufferSize > ((std::numeric_limits<uint32_t>::max)() >> 1))
261 {
262 throw std::bad_alloc();
263 }
264
265 //
266 // grow buffer by 50% (at least 4096 bytes for initial buffer)
267 // and enough to store left overs of specified buffer
268 //
269 _bufferSize += _bufferSize ? _bufferSize / 2 : 4096;
270 _bufferSize = (std::max)(_bufferSize, size);
271
272 _buffer = boost::allocate_shared_noinit<char[]>(_allocator, _bufferSize);
273
274 //
275 // init range
276 //
277 _rangeOffset = 0;
278 _rangePtr = _buffer.get();
279 _rangeSize = size;
280
281 //
282 // copy to the tail of current range
283 std::memcpy(_rangePtr,
284 buffer,
285 size);
286 }
287 }
288
289 void Write(const blob& buffer)
290 {
291 if (buffer.size() < _minChainningSize || _blobs.size() >= _maxChainLength)
292 {
293 // For small amount of data it's faster to memcpy it than chain blob
294 return Write(buffer.data(), buffer.size());
295 }
296
297 //
298 // Internal list of blobs must represent valid sequence of bytes
299
300 //
301 // First snap current buffer range, if it is not empty
302 //
303 if (_rangeSize > 0)
304 {
305 _blobs.emplace_back(_buffer, _rangeOffset, _rangeSize);
306
307 _rangeOffset += _rangeSize;
308 _rangePtr += _rangeSize;
309
310 //
311 // reset range size
312 //
313 _rangeSize = 0;
314 }
315
316 //
317 // attach specified blob to the end of the list
318 //
319 _blobs.push_back(buffer);
320 }
321
322 void Flush()
323 {
324 //
325 // nop
326 //
327 }
328
329 template<typename T>
330 void WriteVariableUnsigned(T value)
331 {
332 if (sizeof(T) * 8 / 7 + _rangeSize + _rangeOffset < _bufferSize)
333 {
334 char* ptr = _rangePtr + _rangeSize;
335 _rangeSize += output_buffer::VariableUnsignedUnchecked<T, 1>::Write(ptr, value);
336 }
337 else
338 {
339 GenericWriteVariableUnsigned(*this, value);
340 }
341 }
342
343protected:
344 // allocator instance
345 A _allocator;
346
347 // current buffer
348 boost::shared_ptr<char[]> _buffer;
349
350 // size of current buffer
351 uint32_t _bufferSize;
352
353 // size of current buffer range
354 uint32_t _rangeSize;
355
356 // offset of current buffer range
357 uint32_t _rangeOffset;
358
359 // smallest blob size that will be chained rather than copied
360 uint32_t _minChainningSize;
361
362 // maximum length of the chains of buffers
363 uint32_t _maxChainLength;
364
365 // pointer of current buffer range
366 char* _rangePtr;
367
368 // list of blobs
369 std::vector<blob, typename std::allocator_traits<A>::template rebind_alloc<blob> > _blobs;
370
371
372 friend OutputMemoryStream CreateOutputBuffer(const OutputMemoryStream& other)
373 {
374 return OutputMemoryStream(
375 other._bufferSize,
376 static_cast<uint32_t>(other._blobs.capacity()),
377 other._allocator,
378 other._minChainningSize,
379 other._maxChainLength);
380 }
381
382}; // class OutputMemoryStream
383
384
387
388} // namespace bond
Memory backed output stream.
Definition: output_buffer.h:89
blob GetBuffer() const
Get content of the stream as one contiguous memory blob.
Definition: output_buffer.h:172
OutputMemoryStream(const A &allocator=A())
Construct OutputMemoryStream using specified allocator instance.
Definition: output_buffer.h:92
void GetBuffers(T &buffers) const
Get content of the stream as a collection of memory blobs.
Definition: output_buffer.h:150
OutputMemoryStream(const boost::shared_ptr< char[]> &buffer, uint32_t size, uint32_t reserveBlobs=128, const A &allocator=A(), uint32_t minChanningSize=32, uint32_t maxChainLength=(uint32_t) -1)
Construct OutputMemoryStream from the first buffer of the specified size and a preallocated vector to...
Definition: output_buffer.h:106
OutputMemoryStream(uint32_t reserveSize, uint32_t reserveBlobs=128, const A &allocator=A(), uint32_t minChanningSize=32, uint32_t maxChainLength=(uint32_t) -1)
Construct OutputMemoryStream with the first buffer of the specified size and a preallocated vector to...
Definition: output_buffer.h:128
Memory blob.
Definition: blob.h:24
namespace bond
Definition: apply.h:17
OutputMemoryStream OutputBuffer
Type alias for memory backed output stream using std::allocator.
Definition: output_buffer.h:386