ProfileChunkedBufferDetail.h (14761B)
1 /* -*- Mode: C++; tab-width: 2; 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 ProfileChunkedBufferDetail_h 8 #define ProfileChunkedBufferDetail_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Likely.h" 12 #include "mozilla/ProfileBufferChunk.h" 13 #include "mozilla/ProfileBufferEntrySerialization.h" 14 15 namespace mozilla::profiler::detail { 16 17 // Internal accessor pointing at a position inside a chunk. 18 // It can handle two groups of chunks (typically the extant chunks stored in 19 // the store manager, and the current chunk). 20 // The main operations are: 21 // - ReadEntrySize() to read an entry size, 0 means failure. 22 // - operator+=(Length) to skip a number of bytes. 23 // - EntryReader() creates an entry reader at the current position for a given 24 // size (it may fail with an empty reader), and skips the entry. 25 // Note that there is no "past-the-end" position -- as soon as InChunkPointer 26 // reaches the end, it becomes effectively null. 27 class InChunkPointer { 28 public: 29 using Byte = ProfileBufferChunk::Byte; 30 using Length = ProfileBufferChunk::Length; 31 32 // Nullptr-like InChunkPointer, may be used as end iterator. 33 InChunkPointer() 34 : mChunk(nullptr), mNextChunkGroup(nullptr), mOffsetInChunk(0) {} 35 36 // InChunkPointer over one or two chunk groups, pointing at the given 37 // block index (if still in range). 38 // This constructor should only be used with *trusted* block index values! 39 InChunkPointer(const ProfileBufferChunk* aChunk, 40 const ProfileBufferChunk* aNextChunkGroup, 41 ProfileBufferBlockIndex aBlockIndex) 42 : mChunk(aChunk), mNextChunkGroup(aNextChunkGroup) { 43 if (mChunk) { 44 mOffsetInChunk = mChunk->OffsetFirstBlock(); 45 Adjust(); 46 } else if (mNextChunkGroup) { 47 mChunk = mNextChunkGroup; 48 mNextChunkGroup = nullptr; 49 mOffsetInChunk = mChunk->OffsetFirstBlock(); 50 Adjust(); 51 } else { 52 mOffsetInChunk = 0; 53 } 54 55 // Try to advance to given position. 56 if (!AdvanceToGlobalRangePosition(aBlockIndex)) { 57 // Block does not exist anymore (or block doesn't look valid), reset the 58 // in-chunk pointer. 59 mChunk = nullptr; 60 mNextChunkGroup = nullptr; 61 } 62 } 63 64 // InChunkPointer over one or two chunk groups, will start at the first 65 // block (if any). This may be slow, so avoid using it too much. 66 InChunkPointer(const ProfileBufferChunk* aChunk, 67 const ProfileBufferChunk* aNextChunkGroup, 68 ProfileBufferIndex aIndex = ProfileBufferIndex(0)) 69 : mChunk(aChunk), mNextChunkGroup(aNextChunkGroup) { 70 if (mChunk) { 71 mOffsetInChunk = mChunk->OffsetFirstBlock(); 72 Adjust(); 73 } else if (mNextChunkGroup) { 74 mChunk = mNextChunkGroup; 75 mNextChunkGroup = nullptr; 76 mOffsetInChunk = mChunk->OffsetFirstBlock(); 77 Adjust(); 78 } else { 79 mOffsetInChunk = 0; 80 } 81 82 // Try to advance to given position. 83 if (!AdvanceToGlobalRangePosition(aIndex)) { 84 // Block does not exist anymore, reset the in-chunk pointer. 85 mChunk = nullptr; 86 mNextChunkGroup = nullptr; 87 } 88 } 89 90 // Compute the current position in the global range. 91 // 0 if null (including if we're reached the end). 92 [[nodiscard]] ProfileBufferIndex GlobalRangePosition() const { 93 if (IsNull()) { 94 return 0; 95 } 96 return mChunk->RangeStart() + mOffsetInChunk; 97 } 98 99 // Move InChunkPointer forward to the block at the given global block 100 // position, which is assumed to be valid exactly -- but it may be obsolete. 101 // 0 stays where it is (if valid already). 102 // MOZ_ASSERTs if the index is invalid. 103 [[nodiscard]] bool AdvanceToGlobalRangePosition( 104 ProfileBufferBlockIndex aBlockIndex) { 105 if (IsNull()) { 106 // Pointer is null already. (Not asserting because it's acceptable.) 107 return false; 108 } 109 if (!aBlockIndex) { 110 // Special null position, just stay where we are. 111 return ShouldPointAtValidBlock(); 112 } 113 if (aBlockIndex.ConvertToProfileBufferIndex() < GlobalRangePosition()) { 114 // Past the requested position, stay where we are (assuming the current 115 // position was valid). 116 return ShouldPointAtValidBlock(); 117 } 118 for (;;) { 119 if (aBlockIndex.ConvertToProfileBufferIndex() < 120 mChunk->RangeStart() + mChunk->OffsetPastLastBlock()) { 121 // Target position is in this chunk's written space, move to it. 122 mOffsetInChunk = 123 aBlockIndex.ConvertToProfileBufferIndex() - mChunk->RangeStart(); 124 return ShouldPointAtValidBlock(); 125 } 126 // Position is after this chunk, try next chunk. 127 GoToNextChunk(); 128 if (IsNull()) { 129 return false; 130 } 131 // Skip whatever block tail there is, we don't allow pointing in the 132 // middle of a block. 133 mOffsetInChunk = mChunk->OffsetFirstBlock(); 134 if (aBlockIndex.ConvertToProfileBufferIndex() < GlobalRangePosition()) { 135 // Past the requested position, meaning that the given position was in- 136 // between blocks -> Failure. 137 MOZ_ASSERT(false, "AdvanceToGlobalRangePosition - In-between blocks"); 138 return false; 139 } 140 } 141 } 142 143 // Move InChunkPointer forward to the block at or after the given global 144 // range position. 145 // 0 stays where it is (if valid already). 146 [[nodiscard]] bool AdvanceToGlobalRangePosition( 147 ProfileBufferIndex aPosition) { 148 if (aPosition == 0) { 149 // Special position '0', just stay where we are. 150 // Success if this position is already valid. 151 return !IsNull(); 152 } 153 for (;;) { 154 ProfileBufferIndex currentPosition = GlobalRangePosition(); 155 if (currentPosition == 0) { 156 // Pointer is null. 157 return false; 158 } 159 if (aPosition <= currentPosition) { 160 // At or past the requested position, stay where we are. 161 return true; 162 } 163 if (aPosition < mChunk->RangeStart() + mChunk->OffsetPastLastBlock()) { 164 // Target position is in this chunk's written space, move to it. 165 for (;;) { 166 // Skip the current block. 167 mOffsetInChunk += ReadEntrySize(); 168 if (mOffsetInChunk >= mChunk->OffsetPastLastBlock()) { 169 // Reached the end of the chunk, this can happen for the last 170 // block, let's just continue to the next chunk. 171 break; 172 } 173 if (aPosition <= mChunk->RangeStart() + mOffsetInChunk) { 174 // We're at or after the position, return at this block position. 175 return true; 176 } 177 } 178 } 179 // Position is after this chunk, try next chunk. 180 GoToNextChunk(); 181 if (IsNull()) { 182 return false; 183 } 184 // Skip whatever block tail there is, we don't allow pointing in the 185 // middle of a block. 186 mOffsetInChunk = mChunk->OffsetFirstBlock(); 187 } 188 } 189 190 [[nodiscard]] Byte ReadByte() { 191 MOZ_ASSERT(!IsNull()); 192 MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock()); 193 Byte byte = mChunk->ByteAt(mOffsetInChunk); 194 if (MOZ_UNLIKELY(++mOffsetInChunk == mChunk->OffsetPastLastBlock())) { 195 Adjust(); 196 } 197 return byte; 198 } 199 200 // Read and skip a ULEB128-encoded size. 201 // 0 means failure (0-byte entries are not allowed.) 202 // Note that this doesn't guarantee that there are actually that many bytes 203 // available to read! (EntryReader() below may gracefully fail.) 204 [[nodiscard]] Length ReadEntrySize() { 205 ULEB128Reader<Length> reader; 206 if (IsNull()) { 207 return 0; 208 } 209 for (;;) { 210 const bool isComplete = reader.FeedByteIsComplete(ReadByte()); 211 if (MOZ_UNLIKELY(IsNull())) { 212 // End of chunks, so there's no actual entry after this anyway. 213 return 0; 214 } 215 if (MOZ_LIKELY(isComplete)) { 216 if (MOZ_UNLIKELY(reader.Value() > mChunk->BufferBytes())) { 217 // Don't allow entries larger than a chunk. 218 return 0; 219 } 220 return reader.Value(); 221 } 222 } 223 } 224 225 InChunkPointer& operator+=(Length aLength) { 226 MOZ_ASSERT(!IsNull()); 227 mOffsetInChunk += aLength; 228 Adjust(); 229 return *this; 230 } 231 232 [[nodiscard]] ProfileBufferEntryReader EntryReader(Length aLength) { 233 if (IsNull() || aLength == 0) { 234 return ProfileBufferEntryReader(); 235 } 236 237 MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock()); 238 239 // We should be pointing at the entry, past the entry size. 240 const ProfileBufferIndex entryIndex = GlobalRangePosition(); 241 // Verify that there's enough space before for the size (starting at index 242 // 1 at least). 243 MOZ_ASSERT(entryIndex >= 1u + ULEB128Size(aLength)); 244 245 const Length remaining = mChunk->OffsetPastLastBlock() - mOffsetInChunk; 246 Span<const Byte> mem0 = mChunk->BufferSpan(); 247 mem0 = mem0.From(mOffsetInChunk); 248 if (aLength <= remaining) { 249 // Move to the end of this block, which could make this null if we have 250 // reached the end of all buffers. 251 *this += aLength; 252 return ProfileBufferEntryReader( 253 mem0.To(aLength), 254 // Block starts before the entry size. 255 ProfileBufferBlockIndex::CreateFromProfileBufferIndex( 256 entryIndex - ULEB128Size(aLength)), 257 // Block ends right after the entry (could be null for last entry). 258 ProfileBufferBlockIndex::CreateFromProfileBufferIndex( 259 GlobalRangePosition())); 260 } 261 262 // We need to go to the next chunk for the 2nd part of this block. 263 GoToNextChunk(); 264 if (IsNull()) { 265 return ProfileBufferEntryReader(); 266 } 267 268 Span<const Byte> mem1 = mChunk->BufferSpan(); 269 const Length tail = aLength - remaining; 270 MOZ_ASSERT(tail <= mChunk->BufferBytes()); 271 MOZ_ASSERT(tail == mChunk->OffsetFirstBlock()); 272 // We are in the correct chunk, move the offset to the end of the block. 273 mOffsetInChunk = tail; 274 // And adjust as needed, which could make this null if we have reached the 275 // end of all buffers. 276 Adjust(); 277 return ProfileBufferEntryReader( 278 mem0, mem1.To(tail), 279 // Block starts before the entry size. 280 ProfileBufferBlockIndex::CreateFromProfileBufferIndex( 281 entryIndex - ULEB128Size(aLength)), 282 // Block ends right after the entry (could be null for last entry). 283 ProfileBufferBlockIndex::CreateFromProfileBufferIndex( 284 GlobalRangePosition())); 285 } 286 287 [[nodiscard]] bool IsNull() const { return !mChunk; } 288 289 [[nodiscard]] bool operator==(const InChunkPointer& aOther) const { 290 if (IsNull() || aOther.IsNull()) { 291 return IsNull() && aOther.IsNull(); 292 } 293 return mChunk == aOther.mChunk && mOffsetInChunk == aOther.mOffsetInChunk; 294 } 295 296 [[nodiscard]] bool operator!=(const InChunkPointer& aOther) const { 297 return !(*this == aOther); 298 } 299 300 [[nodiscard]] Byte operator*() const { 301 MOZ_ASSERT(!IsNull()); 302 MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock()); 303 return mChunk->ByteAt(mOffsetInChunk); 304 } 305 306 InChunkPointer& operator++() { 307 MOZ_ASSERT(!IsNull()); 308 MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock()); 309 if (MOZ_UNLIKELY(++mOffsetInChunk == mChunk->OffsetPastLastBlock())) { 310 mOffsetInChunk = 0; 311 GoToNextChunk(); 312 Adjust(); 313 } 314 return *this; 315 } 316 317 private: 318 void GoToNextChunk() { 319 MOZ_ASSERT(!IsNull()); 320 const ProfileBufferIndex expectedNextRangeStart = 321 mChunk->RangeStart() + mChunk->BufferBytes(); 322 323 mChunk = mChunk->GetNext(); 324 if (!mChunk) { 325 // Reached the end of the current chunk group, try the next one (which 326 // may be null too, especially on the 2nd try). 327 mChunk = mNextChunkGroup; 328 mNextChunkGroup = nullptr; 329 } 330 331 if (mChunk && mChunk->RangeStart() == 0) { 332 // Reached a chunk without a valid (non-null) range start, assume there 333 // are only unused chunks from here on. 334 mChunk = nullptr; 335 } 336 337 MOZ_ASSERT(!mChunk || mChunk->RangeStart() == expectedNextRangeStart, 338 "We don't handle discontinuous buffers (yet)"); 339 // Non-DEBUG fallback: Stop reading past discontinuities. 340 // (They should be rare, only happening on temporary OOMs.) 341 // TODO: Handle discontinuities (by skipping over incomplete blocks). 342 if (mChunk && mChunk->RangeStart() != expectedNextRangeStart) { 343 mChunk = nullptr; 344 } 345 } 346 347 // We want `InChunkPointer` to always point at a valid byte (or be null). 348 // After some operations, `mOffsetInChunk` may point past the end of the 349 // current `mChunk`, in which case we need to adjust our position to be inside 350 // the appropriate chunk. E.g., if we're 10 bytes after the end of the current 351 // chunk, we should end up at offset 10 in the next chunk. 352 // Note that we may "fall off" the last chunk and make this `InChunkPointer` 353 // effectively null. 354 void Adjust() { 355 while (mChunk && mOffsetInChunk >= mChunk->OffsetPastLastBlock()) { 356 // TODO: Try to adjust offset between chunks relative to mRangeStart 357 // differences. But we don't handle discontinuities yet. 358 if (mOffsetInChunk < mChunk->BufferBytes()) { 359 mOffsetInChunk -= mChunk->BufferBytes(); 360 } else { 361 mOffsetInChunk -= mChunk->OffsetPastLastBlock(); 362 } 363 GoToNextChunk(); 364 } 365 } 366 367 // Check if the current position is likely to point at a valid block. 368 // (Size should be reasonable, and block should fully fit inside buffer.) 369 // MOZ_ASSERTs on failure, to catch incorrect uses of block indices (which 370 // should only point at valid blocks if still in range). Non-asserting build 371 // fallback should still be handled. 372 [[nodiscard]] bool ShouldPointAtValidBlock() const { 373 if (IsNull()) { 374 // Pointer is null, no blocks here. 375 MOZ_ASSERT(false, "ShouldPointAtValidBlock - null pointer"); 376 return false; 377 } 378 // Use a copy, so we don't modify `*this`. 379 InChunkPointer pointer = *this; 380 // Try to read the entry size. 381 Length entrySize = pointer.ReadEntrySize(); 382 if (entrySize == 0) { 383 // Entry size of zero means we read 0 or a way-too-big value. 384 MOZ_ASSERT(false, "ShouldPointAtValidBlock - invalid size"); 385 return false; 386 } 387 // See if the last byte of the entry is still inside the buffer. 388 pointer += entrySize - 1; 389 MOZ_ASSERT(!pointer.IsNull(), 390 "ShouldPointAtValidBlock - past end of buffer"); 391 return !pointer.IsNull(); 392 } 393 394 const ProfileBufferChunk* mChunk; 395 const ProfileBufferChunk* mNextChunkGroup; 396 Length mOffsetInChunk; 397 }; 398 399 } // namespace mozilla::profiler::detail 400 401 #endif // ProfileChunkedBufferDetail_h