BufferList.h (20057B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_BufferList_h 8 #define mozilla_BufferList_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/MemoryReporting.h" 12 #include "mozilla/Vector.h" 13 14 #include <algorithm> 15 #include <cstdint> 16 #include <cstring> 17 #include <type_traits> 18 #include <utility> 19 20 // BufferList represents a sequence of buffers of data. A BufferList can choose 21 // to own its buffers or not. The class handles writing to the buffers, 22 // iterating over them, and reading data out. Unlike SegmentedVector, the 23 // buffers may be of unequal size. Like SegmentedVector, BufferList is a nice 24 // way to avoid large contiguous allocations (which can trigger OOMs). 25 26 class InfallibleAllocPolicy; 27 28 namespace mozilla { 29 30 template <typename AllocPolicy> 31 class BufferList : private AllocPolicy { 32 // Each buffer in a BufferList has a size and a capacity. The first mSize 33 // bytes are initialized and the remaining |mCapacity - mSize| bytes are free. 34 struct Segment { 35 char* mData; 36 size_t mSize; 37 size_t mCapacity; 38 39 Segment(char* aData, size_t aSize, size_t aCapacity) 40 : mData(aData), mSize(aSize), mCapacity(aCapacity) {} 41 42 Segment(const Segment&) = delete; 43 Segment& operator=(const Segment&) = delete; 44 45 Segment(Segment&&) = default; 46 Segment& operator=(Segment&&) = default; 47 48 char* Start() const { return mData; } 49 char* End() const { return mData + mSize; } 50 }; 51 52 template <typename OtherAllocPolicy> 53 friend class BufferList; 54 55 public: 56 // For the convenience of callers, all segments are required to be a multiple 57 // of 8 bytes in capacity. Also, every buffer except the last one is required 58 // to be full (i.e., size == capacity). Therefore, a byte at offset N within 59 // the BufferList and stored in memory at an address A will satisfy 60 // (N % Align == A % Align) if Align == 2, 4, or 8. 61 static const size_t kSegmentAlignment = 8; 62 63 // Allocate a BufferList. The BufferList will free all its buffers when it is 64 // destroyed. If an infallible allocator is used, an initial buffer of size 65 // aInitialSize and capacity aInitialCapacity is allocated automatically. This 66 // data will be contiguous and can be accessed via |Start()|. If a fallible 67 // alloc policy is used, aInitialSize must be 0, and the fallible |Init()| 68 // method may be called instead. Subsequent buffers will be allocated with 69 // capacity aStandardCapacity. 70 BufferList(size_t aInitialSize, size_t aInitialCapacity, 71 size_t aStandardCapacity, AllocPolicy aAP = AllocPolicy()) 72 : AllocPolicy(aAP), 73 mOwning(true), 74 mSegments(aAP), 75 mSize(0), 76 mStandardCapacity(aStandardCapacity) { 77 MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0); 78 MOZ_ASSERT(aStandardCapacity % kSegmentAlignment == 0); 79 80 if (aInitialCapacity) { 81 MOZ_ASSERT((aInitialSize == 0 || 82 std::is_same_v<AllocPolicy, InfallibleAllocPolicy>), 83 "BufferList may only be constructed with an initial size when " 84 "using an infallible alloc policy"); 85 86 AllocateSegment(aInitialSize, aInitialCapacity); 87 } 88 } 89 90 BufferList(const BufferList& aOther) = delete; 91 92 BufferList(BufferList&& aOther) 93 : mOwning(aOther.mOwning), 94 mSegments(std::move(aOther.mSegments)), 95 mSize(aOther.mSize), 96 mStandardCapacity(aOther.mStandardCapacity) { 97 aOther.mSegments.clear(); 98 aOther.mSize = 0; 99 } 100 101 BufferList& operator=(const BufferList& aOther) = delete; 102 103 BufferList& operator=(BufferList&& aOther) { 104 Clear(); 105 106 mOwning = aOther.mOwning; 107 mSegments = std::move(aOther.mSegments); 108 mSize = aOther.mSize; 109 aOther.mSegments.clear(); 110 aOther.mSize = 0; 111 return *this; 112 } 113 114 ~BufferList() { Clear(); } 115 116 // Initializes the BufferList with a segment of the given size and capacity. 117 // May only be called once, before any segments have been allocated. 118 bool Init(size_t aInitialSize, size_t aInitialCapacity) { 119 MOZ_ASSERT(mSegments.empty()); 120 MOZ_ASSERT(aInitialCapacity != 0); 121 MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0); 122 123 return AllocateSegment(aInitialSize, aInitialCapacity); 124 } 125 126 bool CopyFrom(const BufferList& aOther) { 127 MOZ_ASSERT(mOwning); 128 129 Clear(); 130 131 // We don't make an exact copy of aOther. Instead, create a single segment 132 // with enough space to hold all data in aOther. 133 if (!Init(aOther.mSize, (aOther.mSize + kSegmentAlignment - 1) & 134 ~(kSegmentAlignment - 1))) { 135 return false; 136 } 137 138 size_t offset = 0; 139 for (const Segment& segment : aOther.mSegments) { 140 memcpy(Start() + offset, segment.mData, segment.mSize); 141 offset += segment.mSize; 142 } 143 MOZ_ASSERT(offset == mSize); 144 145 return true; 146 } 147 148 // Returns the sum of the sizes of all the buffers. 149 size_t Size() const { return mSize; } 150 151 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { 152 size_t size = mSegments.sizeOfExcludingThis(aMallocSizeOf); 153 for (Segment& segment : mSegments) { 154 size += aMallocSizeOf(segment.Start()); 155 } 156 return size; 157 } 158 159 void Clear() { 160 if (mOwning) { 161 for (Segment& segment : mSegments) { 162 this->free_(segment.mData, segment.mCapacity); 163 } 164 } 165 mSegments.clear(); 166 167 mSize = 0; 168 } 169 170 // Iterates over bytes in the segments. You can advance it by as many bytes as 171 // you choose. 172 class IterImpl { 173 // Invariants: 174 // (0) mSegment <= bufferList.mSegments.length() 175 // (1) mData <= mDataEnd 176 // (2) If mSegment is not the last segment, mData < mDataEnd 177 uintptr_t mSegment{0}; 178 char* mData{nullptr}; 179 char* mDataEnd{nullptr}; 180 size_t mAbsoluteOffset{0}; 181 182 friend class BufferList; 183 184 public: 185 explicit IterImpl(const BufferList& aBuffers) { 186 if (!aBuffers.mSegments.empty()) { 187 mData = aBuffers.mSegments[0].Start(); 188 mDataEnd = aBuffers.mSegments[0].End(); 189 } 190 } 191 192 // Returns a pointer to the raw data. It is valid to access up to 193 // RemainingInSegment bytes of this buffer. 194 char* Data() const { 195 MOZ_RELEASE_ASSERT(!Done()); 196 return mData; 197 } 198 199 bool operator==(const IterImpl& other) const { 200 return mAbsoluteOffset == other.mAbsoluteOffset; 201 } 202 bool operator!=(const IterImpl& other) const { return !(*this == other); } 203 204 // Returns true if the memory in the range [Data(), Data() + aBytes) is all 205 // part of one contiguous buffer. 206 bool HasRoomFor(size_t aBytes) const { 207 return RemainingInSegment() >= aBytes; 208 } 209 210 // Returns the largest value aBytes for which HasRoomFor(aBytes) will be 211 // true. 212 size_t RemainingInSegment() const { 213 MOZ_RELEASE_ASSERT(mData <= mDataEnd); 214 return mDataEnd - mData; 215 } 216 217 // Returns true if there are at least aBytes entries remaining in the 218 // BufferList after this iterator. 219 bool HasBytesAvailable(const BufferList& aBuffers, size_t aBytes) const { 220 return TotalBytesAvailable(aBuffers) >= aBytes; 221 } 222 223 // Returns the largest value `aBytes` for which HasBytesAvailable(aBytes) 224 // will be true. 225 size_t TotalBytesAvailable(const BufferList& aBuffers) const { 226 return aBuffers.mSize - mAbsoluteOffset; 227 } 228 229 // Advances the iterator by aBytes bytes. aBytes must be less than 230 // RemainingInSegment(). If advancing by aBytes takes the iterator to the 231 // end of a buffer, it will be moved to the beginning of the next buffer 232 // unless it is the last buffer. 233 void Advance(const BufferList& aBuffers, size_t aBytes) { 234 const Segment& segment = aBuffers.mSegments[mSegment]; 235 MOZ_RELEASE_ASSERT(segment.Start() <= mData); 236 MOZ_RELEASE_ASSERT(mData <= mDataEnd); 237 MOZ_RELEASE_ASSERT(mDataEnd == segment.End()); 238 239 MOZ_RELEASE_ASSERT(HasRoomFor(aBytes)); 240 mData += aBytes; 241 mAbsoluteOffset += aBytes; 242 243 if (mData == mDataEnd && mSegment + 1 < aBuffers.mSegments.length()) { 244 mSegment++; 245 const Segment& nextSegment = aBuffers.mSegments[mSegment]; 246 mData = nextSegment.Start(); 247 mDataEnd = nextSegment.End(); 248 MOZ_RELEASE_ASSERT(mData < mDataEnd); 249 } 250 } 251 252 // Advance the iterator by aBytes, possibly crossing segments. This function 253 // returns false if it runs out of buffers to advance through. Otherwise it 254 // returns true. 255 bool AdvanceAcrossSegments(const BufferList& aBuffers, size_t aBytes) { 256 // If we don't need to cross segments, we can directly use `Advance` to 257 // get to our destination. 258 if (MOZ_LIKELY(aBytes <= RemainingInSegment())) { 259 Advance(aBuffers, aBytes); 260 return true; 261 } 262 263 // Check if we have enough bytes to scan this far forward. 264 if (!HasBytesAvailable(aBuffers, aBytes)) { 265 return false; 266 } 267 268 // Compare the distance to our target offset from the end of the 269 // BufferList to the distance from the start of our next segment. 270 // Depending on which is closer, we'll advance either forwards or 271 // backwards. 272 size_t targetOffset = mAbsoluteOffset + aBytes; 273 size_t fromEnd = aBuffers.mSize - targetOffset; 274 if (aBytes - RemainingInSegment() < fromEnd) { 275 // Advance through the buffer list until we reach the desired absolute 276 // offset. 277 while (mAbsoluteOffset < targetOffset) { 278 Advance(aBuffers, std::min(targetOffset - mAbsoluteOffset, 279 RemainingInSegment())); 280 } 281 MOZ_ASSERT(mAbsoluteOffset == targetOffset); 282 return true; 283 } 284 285 // Scanning starting from the end of the BufferList. We advance 286 // backwards from the final segment until we find the segment to end in. 287 // 288 // If we end on a segment boundary, make sure to place the cursor at the 289 // beginning of the next segment. 290 mSegment = aBuffers.mSegments.length() - 1; 291 while (fromEnd > aBuffers.mSegments[mSegment].mSize) { 292 fromEnd -= aBuffers.mSegments[mSegment].mSize; 293 mSegment--; 294 } 295 mDataEnd = aBuffers.mSegments[mSegment].End(); 296 mData = mDataEnd - fromEnd; 297 mAbsoluteOffset = targetOffset; 298 MOZ_ASSERT_IF(Done(), mSegment == aBuffers.mSegments.length() - 1); 299 MOZ_ASSERT_IF(Done(), mAbsoluteOffset == aBuffers.mSize); 300 return true; 301 } 302 303 // Returns true when the iterator reaches the end of the BufferList. 304 bool Done() const { return mData == mDataEnd; } 305 306 // The absolute offset of this iterator within the BufferList. 307 size_t AbsoluteOffset() const { return mAbsoluteOffset; } 308 309 private: 310 bool IsIn(const BufferList& aBuffers) const { 311 return mSegment < aBuffers.mSegments.length() && 312 mData >= aBuffers.mSegments[mSegment].mData && 313 mData < aBuffers.mSegments[mSegment].End(); 314 } 315 }; 316 317 // Special convenience method that returns Iter().Data(). 318 char* Start() { 319 MOZ_RELEASE_ASSERT(!mSegments.empty()); 320 return mSegments[0].mData; 321 } 322 const char* Start() const { return mSegments[0].mData; } 323 324 IterImpl Iter() const { return IterImpl(*this); } 325 326 // Copies aSize bytes from aData into the BufferList. The storage for these 327 // bytes may be split across multiple buffers. Size() is increased by aSize. 328 [[nodiscard]] inline bool WriteBytes(const char* aData, size_t aSize); 329 330 // Allocates a buffer of at most |aMaxBytes| bytes and, if successful, returns 331 // that buffer, and places its size in |aSize|. If unsuccessful, returns null 332 // and leaves |aSize| undefined. 333 inline char* AllocateBytes(size_t aMaxSize, size_t* aSize); 334 335 // Copies possibly non-contiguous byte range starting at aIter into 336 // aData. aIter is advanced by aSize bytes. Returns false if it runs out of 337 // data before aSize. 338 inline bool ReadBytes(IterImpl& aIter, char* aData, size_t aSize) const; 339 340 // Return a new BufferList that shares storage with this BufferList. The new 341 // BufferList is read-only. It allows iteration over aSize bytes starting at 342 // aIter. Borrow can fail, in which case *aSuccess will be false upon 343 // return. The borrowed BufferList can use a different AllocPolicy than the 344 // original one. However, it is not responsible for freeing buffers, so the 345 // AllocPolicy is only used for the buffer vector. 346 template <typename BorrowingAllocPolicy> 347 BufferList<BorrowingAllocPolicy> Borrow( 348 IterImpl& aIter, size_t aSize, bool* aSuccess, 349 BorrowingAllocPolicy aAP = BorrowingAllocPolicy()) const; 350 351 // Return a new BufferList and move storage from this BufferList to it. The 352 // new BufferList owns the buffers. Move can fail, in which case *aSuccess 353 // will be false upon return. The new BufferList can use a different 354 // AllocPolicy than the original one. The new OtherAllocPolicy is responsible 355 // for freeing buffers, so the OtherAllocPolicy must use freeing method 356 // compatible to the original one. 357 template <typename OtherAllocPolicy> 358 BufferList<OtherAllocPolicy> MoveFallible( 359 bool* aSuccess, OtherAllocPolicy aAP = OtherAllocPolicy()); 360 361 // Return the number of bytes from 'start' to 'end', two iterators within 362 // this BufferList. 363 size_t RangeLength(const IterImpl& start, const IterImpl& end) const { 364 MOZ_ASSERT(start.IsIn(*this) && end.IsIn(*this)); 365 return end.mAbsoluteOffset - start.mAbsoluteOffset; 366 } 367 368 // This takes ownership of the data 369 [[nodiscard]] bool WriteBytesZeroCopy(char* aData, size_t aSize, 370 size_t aCapacity) { 371 MOZ_ASSERT(mOwning); 372 MOZ_ASSERT(aSize <= aCapacity); 373 374 // Don't create zero-length segments; that can cause problems for 375 // consumers of the data (bug 1595453). 376 if (aSize == 0) { 377 this->free_(aData, aCapacity); 378 return true; 379 } 380 381 if (!mSegments.append(Segment(aData, aSize, aCapacity))) { 382 this->free_(aData, aCapacity); 383 return false; 384 } 385 mSize += aSize; 386 return true; 387 } 388 389 // Truncate this BufferList at the given iterator location, discarding all 390 // data after this point. After this call, all other iterators will be 391 // invalidated, and the passed-in iterator will be "Done". 392 // 393 // Returns the number of bytes discarded by this truncation. 394 size_t Truncate(IterImpl& aIter); 395 396 private: 397 explicit BufferList(AllocPolicy aAP) 398 : AllocPolicy(aAP), mOwning(false), mSize(0), mStandardCapacity(0) {} 399 400 char* AllocateSegment(size_t aSize, size_t aCapacity) { 401 MOZ_RELEASE_ASSERT(mOwning); 402 MOZ_ASSERT(aCapacity != 0); 403 MOZ_ASSERT(aSize <= aCapacity); 404 405 char* data = this->template pod_malloc<char>(aCapacity); 406 if (!data) { 407 return nullptr; 408 } 409 if (!mSegments.append(Segment(data, aSize, aCapacity))) { 410 this->free_(data, aCapacity); 411 return nullptr; 412 } 413 mSize += aSize; 414 return data; 415 } 416 417 void AssertConsistentSize() const { 418 #ifdef DEBUG 419 size_t realSize = 0; 420 for (const auto& segment : mSegments) { 421 realSize += segment.mSize; 422 } 423 MOZ_ASSERT(realSize == mSize, "cached size value is inconsistent!"); 424 #endif 425 } 426 427 bool mOwning; 428 Vector<Segment, 1, AllocPolicy> mSegments; 429 size_t mSize; 430 size_t mStandardCapacity; 431 }; 432 433 template <typename AllocPolicy> 434 [[nodiscard]] bool BufferList<AllocPolicy>::WriteBytes(const char* aData, 435 size_t aSize) { 436 MOZ_RELEASE_ASSERT(mOwning); 437 MOZ_RELEASE_ASSERT(mStandardCapacity); 438 439 size_t copied = 0; 440 while (copied < aSize) { 441 size_t toCopy; 442 char* data = AllocateBytes(aSize - copied, &toCopy); 443 if (!data) { 444 return false; 445 } 446 memcpy(data, aData + copied, toCopy); 447 copied += toCopy; 448 } 449 450 return true; 451 } 452 453 template <typename AllocPolicy> 454 char* BufferList<AllocPolicy>::AllocateBytes(size_t aMaxSize, size_t* aSize) { 455 MOZ_RELEASE_ASSERT(mOwning); 456 MOZ_RELEASE_ASSERT(mStandardCapacity); 457 458 if (!mSegments.empty()) { 459 Segment& lastSegment = mSegments.back(); 460 461 size_t capacity = lastSegment.mCapacity - lastSegment.mSize; 462 if (capacity) { 463 size_t size = std::min(aMaxSize, capacity); 464 char* data = lastSegment.mData + lastSegment.mSize; 465 466 lastSegment.mSize += size; 467 mSize += size; 468 469 *aSize = size; 470 return data; 471 } 472 } 473 474 size_t size = std::min(aMaxSize, mStandardCapacity); 475 char* data = AllocateSegment(size, mStandardCapacity); 476 if (data) { 477 *aSize = size; 478 } 479 return data; 480 } 481 482 template <typename AllocPolicy> 483 bool BufferList<AllocPolicy>::ReadBytes(IterImpl& aIter, char* aData, 484 size_t aSize) const { 485 size_t copied = 0; 486 size_t remaining = aSize; 487 while (remaining) { 488 size_t toCopy = std::min(aIter.RemainingInSegment(), remaining); 489 if (!toCopy) { 490 // We've run out of data in the last segment. 491 return false; 492 } 493 memcpy(aData + copied, aIter.Data(), toCopy); 494 copied += toCopy; 495 remaining -= toCopy; 496 497 aIter.Advance(*this, toCopy); 498 } 499 500 return true; 501 } 502 503 template <typename AllocPolicy> 504 template <typename BorrowingAllocPolicy> 505 BufferList<BorrowingAllocPolicy> BufferList<AllocPolicy>::Borrow( 506 IterImpl& aIter, size_t aSize, bool* aSuccess, 507 BorrowingAllocPolicy aAP) const { 508 BufferList<BorrowingAllocPolicy> result(aAP); 509 510 size_t size = aSize; 511 while (size) { 512 size_t toAdvance = std::min(size, aIter.RemainingInSegment()); 513 514 if (!toAdvance || !result.mSegments.append( 515 typename BufferList<BorrowingAllocPolicy>::Segment( 516 aIter.mData, toAdvance, toAdvance))) { 517 *aSuccess = false; 518 return result; 519 } 520 aIter.Advance(*this, toAdvance); 521 size -= toAdvance; 522 } 523 524 result.mSize = aSize; 525 *aSuccess = true; 526 return result; 527 } 528 529 template <typename AllocPolicy> 530 template <typename OtherAllocPolicy> 531 BufferList<OtherAllocPolicy> BufferList<AllocPolicy>::MoveFallible( 532 bool* aSuccess, OtherAllocPolicy aAP) { 533 BufferList<OtherAllocPolicy> result(0, 0, mStandardCapacity, aAP); 534 535 IterImpl iter = Iter(); 536 while (!iter.Done()) { 537 size_t toAdvance = iter.RemainingInSegment(); 538 539 if (!toAdvance || 540 !result.mSegments.append(typename BufferList<OtherAllocPolicy>::Segment( 541 iter.mData, toAdvance, toAdvance))) { 542 *aSuccess = false; 543 result.mSegments.clear(); 544 return result; 545 } 546 iter.Advance(*this, toAdvance); 547 } 548 549 result.mSize = mSize; 550 mSegments.clear(); 551 mSize = 0; 552 *aSuccess = true; 553 return result; 554 } 555 556 template <typename AllocPolicy> 557 size_t BufferList<AllocPolicy>::Truncate(IterImpl& aIter) { 558 MOZ_ASSERT(aIter.IsIn(*this) || aIter.Done()); 559 if (aIter.Done()) { 560 return 0; 561 } 562 563 size_t prevSize = mSize; 564 565 // Remove any segments after the iterator's current segment. 566 while (mSegments.length() > aIter.mSegment + 1) { 567 Segment& toFree = mSegments.back(); 568 mSize -= toFree.mSize; 569 if (mOwning) { 570 this->free_(toFree.mData, toFree.mCapacity); 571 } 572 mSegments.popBack(); 573 } 574 575 // The last segment is now aIter's current segment. Truncate or remove it. 576 Segment& seg = mSegments.back(); 577 MOZ_ASSERT(aIter.mDataEnd == seg.End()); 578 mSize -= aIter.RemainingInSegment(); 579 seg.mSize -= aIter.RemainingInSegment(); 580 if (!seg.mSize) { 581 if (mOwning) { 582 this->free_(seg.mData, seg.mCapacity); 583 } 584 mSegments.popBack(); 585 } 586 587 // Correct `aIter` to point to the new end of the BufferList. 588 if (mSegments.empty()) { 589 MOZ_ASSERT(mSize == 0); 590 aIter.mSegment = 0; 591 aIter.mData = aIter.mDataEnd = nullptr; 592 } else { 593 aIter.mSegment = mSegments.length() - 1; 594 aIter.mData = aIter.mDataEnd = mSegments.back().End(); 595 } 596 MOZ_ASSERT(aIter.Done()); 597 598 AssertConsistentSize(); 599 return prevSize - mSize; 600 } 601 602 } // namespace mozilla 603 604 #endif /* mozilla_BufferList_h */