/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ModuloBuffer_h #define ModuloBuffer_h #include "mozilla/leb128iterator.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/NotNull.h" #include "mozilla/PowerOfTwo.h" #include "mozilla/ProfileBufferEntrySerialization.h" #include "mozilla/UniquePtr.h" #include #include #include #include namespace mozilla { // The ModuloBuffer class is a circular buffer that holds raw byte values, with // data-read/write helpers. // // OffsetT: Type of the internal offset into the buffer of bytes, it should be // large enough to access all bytes of the buffer. It will also be used as // Length (in bytes) of the buffer and of any subset. Default uint32_t // IndexT: Type of the external index, it should be large enough that overflows // should not happen during the lifetime of the ModuloBuffer. // // The basic usage is to create an iterator-like object with `ReaderAt(Index)` // or `WriterAt(Index)`, and use it to read/write data blobs. Iterators // automatically manage the wrap-around (through "Modulo", which is effectively // an AND-masking with the PowerOfTwo buffer size.) // // There is zero safety: No thread safety, no checks that iterators may be // overwriting data that's still to be read, etc. It's up to the caller to add // adequate checks. // The intended use is as an underlying buffer for a safer container. template class ModuloBuffer { public: using Byte = uint8_t; static_assert(sizeof(Byte) == 1, "ModuloBuffer::Byte must be 1 byte"); using Offset = OffsetT; static_assert(!std::numeric_limits::is_signed, "ModuloBuffer::Offset must be an unsigned integral type"); using Length = Offset; using Index = IndexT; static_assert(!std::numeric_limits::is_signed, "ModuloBuffer::Index must be an unsigned integral type"); static_assert(sizeof(Index) >= sizeof(Offset), "ModuloBuffer::Index size must >= Offset"); // Create a buffer of the given length. explicit ModuloBuffer(PowerOfTwo aLength) : mMask(aLength.Mask()), mBuffer(WrapNotNull(new Byte[aLength.Value()])), mBufferDeleter([](Byte* aBuffer) { delete[] aBuffer; }) {} // Take ownership of an existing buffer. Existing contents is ignored. // Done by extracting the raw pointer from UniquePtr, and adding // an equivalent `delete[]` in `mBufferDeleter`. ModuloBuffer(UniquePtr aExistingBuffer, PowerOfTwo aLength) : mMask(aLength.Mask()), mBuffer(WrapNotNull(aExistingBuffer.release())), mBufferDeleter([](Byte* aBuffer) { delete[] aBuffer; }) {} // Use an externally-owned buffer. Existing contents is ignored. ModuloBuffer(Byte* aExternalBuffer, PowerOfTwo aLength) : mMask(aLength.Mask()), mBuffer(WrapNotNull(aExternalBuffer)) {} // Disallow copying, as we may uniquely own the resource. ModuloBuffer(const ModuloBuffer& aOther) = delete; ModuloBuffer& operator=(const ModuloBuffer& aOther) = delete; // Allow move-construction. Stealing ownership if the original had it. // This effectively prevents copy construction, and all assignments; needed so // that a ModuloBuffer may be initialized from a separate construction. // The moved-from ModuloBuffer still points at the resource but doesn't own // it, so it won't try to free it; but accesses are not guaranteed, so it // should not be used anymore. ModuloBuffer(ModuloBuffer&& aOther) : mMask(std::move(aOther.mMask)), mBuffer(std::move(aOther.mBuffer)), mBufferDeleter(std::move(aOther.mBufferDeleter)) { // The above move leaves `aOther.mBufferDeleter` in a valid state but with // an unspecified value, so it could theoretically still contain the // original function, which would be bad because we don't want aOther to // delete the resource that `this` now owns. if (aOther.mBufferDeleter) { // `aOther` still had a non-empty deleter, reset it. aOther.mBufferDeleter = nullptr; } } // Disallow assignment, as we have some `const` members. ModuloBuffer& operator=(ModuloBuffer&& aOther) = delete; // Destructor, deletes the resource if we uniquely own it. ~ModuloBuffer() { if (mBufferDeleter) { mBufferDeleter(mBuffer); } } PowerOfTwo BufferLength() const { return PowerOfTwo(mMask.MaskValue() + 1); } // Size of external resources. // Note: `mBufferDeleter`'s potential external data (for its captures) is not // included, as it's hidden in the `std::function` implementation. size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { if (!mBufferDeleter) { // If we don't have a buffer deleter, assume we don't own the data, so // it's probably on the stack, or should be reported by its owner. return 0; } return aMallocSizeOf(mBuffer); } size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } ProfileBufferEntryReader EntryReaderFromTo( Index aStart, Index aEnd, ProfileBufferBlockIndex aBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) const { using EntrySpan = Span; if (aStart == aEnd) { return ProfileBufferEntryReader{}; } // Don't allow over-wrapping. MOZ_ASSERT(aEnd - aStart <= mMask.MaskValue() + 1); // Start offset in 0 .. (buffer size - 1) Offset start = static_cast(aStart) & mMask; // End offset in 1 .. (buffer size) Offset end = (static_cast(aEnd - 1) & mMask) + 1; if (start < end) { // Segment doesn't cross buffer threshold, one span is enough. return ProfileBufferEntryReader{EntrySpan(&mBuffer[start], end - start), aBlockIndex, aNextBlockIndex}; } // Segment crosses buffer threshold, we need one span until the end and one // span restarting at the beginning of the buffer. return ProfileBufferEntryReader{ EntrySpan(&mBuffer[start], mMask.MaskValue() + 1 - start), EntrySpan(&mBuffer[0], end), aBlockIndex, aNextBlockIndex}; } // Return an entry writer for the given range. ProfileBufferEntryWriter EntryWriterFromTo(Index aStart, Index aEnd) const { using EntrySpan = Span; if (aStart == aEnd) { return ProfileBufferEntryWriter{}; } MOZ_ASSERT(aEnd - aStart <= mMask.MaskValue() + 1); // Start offset in 0 .. (buffer size - 1) Offset start = static_cast(aStart) & mMask; // End offset in 1 .. (buffer size) Offset end = (static_cast(aEnd - 1) & mMask) + 1; if (start < end) { // Segment doesn't cross buffer threshold, one span is enough. return ProfileBufferEntryWriter{ EntrySpan(&mBuffer[start], end - start), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aStart), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aEnd)}; } // Segment crosses buffer threshold, we need one span until the end and one // span restarting at the beginning of the buffer. return ProfileBufferEntryWriter{ EntrySpan(&mBuffer[start], mMask.MaskValue() + 1 - start), EntrySpan(&mBuffer[0], end), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aStart), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aEnd)}; } // Emplace an entry writer into `aMaybeEntryWriter` for the given range. void EntryWriterFromTo(Maybe& aMaybeEntryWriter, Index aStart, Index aEnd) const { MOZ_ASSERT(aMaybeEntryWriter.isNothing(), "Reference entry writer should be Nothing."); using EntrySpan = Span; if (aStart == aEnd) { return; } MOZ_ASSERT(aEnd - aStart <= mMask.MaskValue() + 1); // Start offset in 0 .. (buffer size - 1) Offset start = static_cast(aStart) & mMask; // End offset in 1 .. (buffer size) Offset end = (static_cast(aEnd - 1) & mMask) + 1; if (start < end) { // Segment doesn't cross buffer threshold, one span is enough. aMaybeEntryWriter.emplace( EntrySpan(&mBuffer[start], end - start), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aStart), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aEnd)); } else { // Segment crosses buffer threshold, we need one span until the end and // one span restarting at the beginning of the buffer. aMaybeEntryWriter.emplace( EntrySpan(&mBuffer[start], mMask.MaskValue() + 1 - start), EntrySpan(&mBuffer[0], end), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aStart), ProfileBufferBlockIndex::CreateFromProfileBufferIndex(aEnd)); } } // All ModuloBuffer operations should be done through this iterator, which has // an effectively infinite range. The underlying wrapping-around is hidden. // Use `ReaderAt(Index)` or `WriterAt(Index)` to create it. // // `const Iterator<...>` means the iterator itself cannot change, i.e., it // cannot move, and only its const methods are available. Note that these // const methods may still be used to modify the buffer contents (e.g.: // `operator*()`, `Poke()`). // // `Iterator` means the buffer contents cannot be // modified, i.e., write operations are forbidden, but the iterator may still // move if non-const itself. template class Iterator { // Alias to const- or mutable-`ModuloBuffer` depending on `IsBufferConst`. using ConstOrMutableBuffer = std::conditional_t; // Implementation note about the strange enable-if's below: // `template enable_if_t` // which intuitively could be simplified to: // `enable_if_t` // The former extra-templated syntax is in fact necessary to delay // instantiation of these functions until they are actually needed. // // If we were just doing `enable_if_t`, this would only // depend on the *class* (`ModuloBuffer<...>::Iterator`), which gets // instantiated when a `ModuloBuffer` is created with some template // arguments; at that point, all non-templated methods get instantiated, so // there's no "SFINAE" happening, and `enable_if_t<...>` is actually doing // `typename enable_if<...>::type` on the spot, but there is no `type` if // `IsBufferConst` is true, so it just fails right away. E.g.: // error: no type named 'type' in 'std::enable_if'; // 'enable_if' cannot be used to disable this declaration // note: in instantiation of template type alias 'enable_if_t' // > std::enable_if_t WriteObject(const T& aObject) { // in instantiation of template class // 'mozilla::ModuloBuffer<...>::Iterator' // > auto it = mb.ReaderAt(1); // // By adding another template level `template `, the instantiation is delayed until the function is // actually invoked somewhere, e.g. `it.Poke(...);`. // So at that invocation point, the compiler looks for a "Poke" name in it, // and considers potential template instantiations that could work. The // `enable_if_t` is *now* attempted, with `NotIsBufferConst` taking its // value from `!IsBufferConst`: // - If `IsBufferConst` is false, `NotIsBufferConst` is true, // `enable_if` does define a `type` (`void` by default), // so `enable_if_t` happily becomes `void`, the function exists and may be // called. // - Otherwise if `IsBufferConst` is true, `NotIsBufferConst` is false, // `enable_if` does *not* define a `type`, therefore // `enable_if_t` produces an error because there is no `type`. Now "SFINAE" // happens: This "Substitution Failure Is Not An Error" (by itself)... But // then, there are no other functions named "Poke" as requested in the // `it.Poke(...);` call, so we are now getting an error (can't find // function), as expected because `it` had `IsBufferConst`==true. (But at // least the compiler waited until this invocation attempt before outputting // an error.) // // C++ is fun! public: // These definitions are expected by std functions, to recognize this as an // iterator. See https://en.cppreference.com/w/cpp/iterator/iterator_traits using difference_type = Index; using value_type = Byte; using pointer = std::conditional_t; using reference = std::conditional_t; using iterator_category = std::random_access_iterator_tag; // Can always copy/assign from the same kind of iterator. Iterator(const Iterator& aRhs) = default; Iterator& operator=(const Iterator& aRhs) = default; // Can implicitly copy an Iterator-to-mutable (reader+writer) to // Iterator-to-const (reader-only), but not the reverse. template > MOZ_IMPLICIT Iterator(const Iterator& aRhs) : mModuloBuffer(aRhs.mModuloBuffer), mIndex(aRhs.mIndex) {} // Can implicitly assign from an Iterator-to-mutable (reader+writer) to // Iterator-to-const (reader-only), but not the reverse. template > Iterator& operator=(const Iterator& aRhs) { mModuloBuffer = aRhs.mModuloBuffer; mIndex = aRhs.mIndex; return *this; } // Current location of the iterator in the `Index` range. // Note that due to wrapping, multiple indices may effectively point at the // same byte in the buffer. Index CurrentIndex() const { return mIndex; } // Location comparison in the `Index` range. I.e., two `Iterator`s may look // unequal, but refer to the same buffer location. // Must be on the same buffer. bool operator==(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex == aRhs.mIndex; } bool operator!=(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex != aRhs.mIndex; } bool operator<(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex < aRhs.mIndex; } bool operator<=(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex <= aRhs.mIndex; } bool operator>(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex > aRhs.mIndex; } bool operator>=(const Iterator& aRhs) const { MOZ_ASSERT(mModuloBuffer == aRhs.mModuloBuffer); return mIndex >= aRhs.mIndex; } // Movement in the `Index` range. Iterator& operator++() { ++mIndex; return *this; } Iterator operator++(int) { Iterator here(*mModuloBuffer, mIndex); ++mIndex; return here; } Iterator& operator--() { --mIndex; return *this; } Iterator operator--(int) { Iterator here(*mModuloBuffer, mIndex); --mIndex; return here; } Iterator& operator+=(Length aLength) { mIndex += aLength; return *this; } Iterator operator+(Length aLength) const { return Iterator(*mModuloBuffer, mIndex + aLength); } friend Iterator operator+(Length aLength, const Iterator& aIt) { return aIt + aLength; } Iterator& operator-=(Length aLength) { mIndex -= aLength; return *this; } Iterator operator-(Length aLength) const { return Iterator(*mModuloBuffer, mIndex - aLength); } // Distance from `aRef` to here in the `Index` range. // May be negative (as 2's complement) if `aRef > *this`. Index operator-(const Iterator& aRef) const { MOZ_ASSERT(mModuloBuffer == aRef.mModuloBuffer); return mIndex - aRef.mIndex; } // Dereference a single byte (read-only if `IsBufferConst` is true). reference operator*() const { return mModuloBuffer->mBuffer[OffsetInBuffer()]; } // Random-access dereference. reference operator[](Length aLength) const { return *(*this + aLength); } // Write data (if `IsBufferConst` is false) but don't move iterator. template std::enable_if_t Poke(const void* aSrc, Length aLength) const { // Don't allow data larger than the buffer. MOZ_ASSERT(aLength <= mModuloBuffer->BufferLength().Value()); // Offset inside the buffer (corresponding to our Index). Offset offset = OffsetInBuffer(); // Compute remaining bytes between this offset and the end of the buffer. Length remaining = mModuloBuffer->BufferLength().Value() - offset; if (MOZ_LIKELY(remaining >= aLength)) { // Enough space to write everything before the end. memcpy(&mModuloBuffer->mBuffer[offset], aSrc, aLength); } else { // Not enough space. Write as much as possible before the end. memcpy(&mModuloBuffer->mBuffer[offset], aSrc, remaining); // And then continue from the beginning of the buffer. memcpy(&mModuloBuffer->mBuffer[0], static_cast(aSrc) + remaining, (aLength - remaining)); } } // Write object data (if `IsBufferConst` is false) but don't move iterator. // Note that this copies bytes from the object, with the intent to read them // back later. Restricted to trivially-copyable types, which support this // without Undefined Behavior! template std::enable_if_t PokeObject(const T& aObject) const { static_assert(std::is_trivially_copyable::value, "PokeObject - T must be trivially copyable"); return Poke(&aObject, sizeof(T)); } // Write data (if `IsBufferConst` is false) and move iterator ahead. template std::enable_if_t Write(const void* aSrc, Length aLength) { Poke(aSrc, aLength); mIndex += aLength; } // Write object data (if `IsBufferConst` is false) and move iterator ahead. // Note that this copies bytes from the object, with the intent to read them // back later. Restricted to trivially-copyable types, which support this // without Undefined Behavior! template std::enable_if_t WriteObject(const T& aObject) { static_assert(std::is_trivially_copyable::value, "WriteObject - T must be trivially copyable"); return Write(&aObject, sizeof(T)); } // Number of bytes needed to represent `aValue` in unsigned LEB128. template static unsigned ULEB128Size(T aValue) { return ::mozilla::ULEB128Size(aValue); } // Write number as unsigned LEB128 (if `IsBufferConst` is false) and move // iterator ahead. template std::enable_if_t WriteULEB128(T aValue) { ::mozilla::WriteULEB128(aValue, *this); } // Read data but don't move iterator. void Peek(void* aDst, Length aLength) const { // Don't allow data larger than the buffer. MOZ_ASSERT(aLength <= mModuloBuffer->BufferLength().Value()); // Offset inside the buffer (corresponding to our Index). Offset offset = OffsetInBuffer(); // Compute remaining bytes between this offset and the end of the buffer. Length remaining = mModuloBuffer->BufferLength().Value() - offset; if (MOZ_LIKELY(remaining >= aLength)) { // Can read everything we need before the end of the buffer. memcpy(aDst, &mModuloBuffer->mBuffer[offset], aLength); } else { // Read as much as possible before the end of the buffer. memcpy(aDst, &mModuloBuffer->mBuffer[offset], remaining); // And then continue from the beginning of the buffer. memcpy(static_cast(aDst) + remaining, &mModuloBuffer->mBuffer[0], (aLength - remaining)); } } // Read data into an object but don't move iterator. // Note that this overwrites `aObject` with bytes from the buffer. // Restricted to trivially-copyable types, which support this without // Undefined Behavior! template void PeekIntoObject(T& aObject) const { static_assert(std::is_trivially_copyable::value, "PeekIntoObject - T must be trivially copyable"); Peek(&aObject, sizeof(T)); } // Read data as an object but don't move iterator. // Note that this creates an default `T` first, and then overwrites it with // bytes from the buffer. Restricted to trivially-copyable types, which // support this without Undefined Behavior! template T PeekObject() const { static_assert(std::is_trivially_copyable::value, "PeekObject - T must be trivially copyable"); T object; PeekIntoObject(object); return object; } // Read data and move iterator ahead. void Read(void* aDst, Length aLength) { Peek(aDst, aLength); mIndex += aLength; } // Read data into a mutable iterator and move both iterators ahead. void ReadInto(Iterator& aDst, Length aLength) { // Don't allow data larger than the buffer. MOZ_ASSERT(aLength <= mModuloBuffer->BufferLength().Value()); MOZ_ASSERT(aLength <= aDst.mModuloBuffer->BufferLength().Value()); // Offset inside the buffer (corresponding to our Index). Offset offset = OffsetInBuffer(); // Compute remaining bytes between this offset and the end of the buffer. Length remaining = mModuloBuffer->BufferLength().Value() - offset; if (MOZ_LIKELY(remaining >= aLength)) { // Can read everything we need before the end of the buffer. aDst.Write(&mModuloBuffer->mBuffer[offset], aLength); } else { // Read as much as possible before the end of the buffer. aDst.Write(&mModuloBuffer->mBuffer[offset], remaining); // And then continue from the beginning of the buffer. aDst.Write(&mModuloBuffer->mBuffer[0], (aLength - remaining)); } mIndex += aLength; } // Read data into an object and move iterator ahead. // Note that this overwrites `aObject` with bytes from the buffer. // Restricted to trivially-copyable types, which support this without // Undefined Behavior! template void ReadIntoObject(T& aObject) { static_assert(std::is_trivially_copyable::value, "ReadIntoObject - T must be trivially copyable"); Read(&aObject, sizeof(T)); } // Read data as an object and move iterator ahead. // Note that this creates an default `T` first, and then overwrites it with // bytes from the buffer. Restricted to trivially-copyable types, which // support this without Undefined Behavior! template T ReadObject() { static_assert(std::is_trivially_copyable::value, "ReadObject - T must be trivially copyable"); T object; ReadIntoObject(object); return object; } // Read an unsigned LEB128 number and move iterator ahead. template T ReadULEB128() { return ::mozilla::ReadULEB128(*this); } private: // Only a ModuloBuffer can instantiate its iterator. friend class ModuloBuffer; Iterator(ConstOrMutableBuffer& aBuffer, Index aIndex) : mModuloBuffer(WrapNotNull(&aBuffer)), mIndex(aIndex) {} // Convert the Iterator's mIndex into an offset inside the byte buffer. Offset OffsetInBuffer() const { return static_cast(mIndex) & mModuloBuffer->mMask; } // ModuloBuffer that this Iterator operates on. // Using a non-null pointer instead of a reference, to allow re-assignment // of an Iterator variable. NotNull mModuloBuffer; // Position of this iterator in the wider `Index` range. (Will be wrapped // around as needed when actually accessing bytes from the buffer.) Index mIndex; }; // Shortcut to iterator to const (read-only) data. using Reader = Iterator; // Shortcut to iterator to non-const (read/write) data. using Writer = Iterator; // Create an iterator to const data at the given index. Reader ReaderAt(Index aIndex) const { return Reader(*this, aIndex); } // Create an iterator to non-const data at the given index. Writer WriterAt(Index aIndex) { return Writer(*this, aIndex); } #ifdef DEBUG void Dump() const { Length len = BufferLength().Value(); if (len > 128) { len = 128; } for (Length i = 0; i < len; ++i) { printf("%02x ", mBuffer[i]); } printf("\n"); } #endif // DEBUG private: // Mask used to convert an index to an offset in `mBuffer` const PowerOfTwoMask mMask; // Buffer data. `const NotNull<...>` shows that `mBuffer is `const`, and // `Byte* const` shows that the pointer cannot be changed to point at // something else, but the pointed-at `Byte`s are writable. const NotNull mBuffer; // Function used to release the buffer resource (if needed). std::function mBufferDeleter; }; } // namespace mozilla #endif // ModuloBuffer_h