diff options
Diffstat (limited to 'mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h')
-rw-r--r-- | mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h b/mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h new file mode 100644 index 0000000000..7ba19b070d --- /dev/null +++ b/mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h @@ -0,0 +1,1184 @@ +/* -*- 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 ProfileBufferEntrySerialization_h +#define ProfileBufferEntrySerialization_h + +#include "mozilla/Assertions.h" +#include "mozilla/leb128iterator.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/ProfileBufferIndex.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "mozilla/Variant.h" + +#include <string> +#include <tuple> + +namespace mozilla { + +class ProfileBufferEntryWriter; + +// Iterator-like class used to read from an entry. +// An entry may be split in two memory segments (e.g., the ends of a ring +// buffer, or two chunks of a chunked buffer); it doesn't deal with this +// underlying buffer, but only with one or two spans pointing at the space +// where the entry lives. +class ProfileBufferEntryReader { + public: + using Byte = uint8_t; + using Length = uint32_t; + + using SpanOfConstBytes = Span<const Byte>; + + // Class to be specialized for types to be read from a profile buffer entry. + // See common specializations at the bottom of this header. + // The following static functions must be provided: + // static void ReadInto(EntryReader aER&, T& aT) + // { + // /* Call `aER.ReadX(...)` function to deserialize into aT, be sure to + // read exactly `Bytes(aT)`! */ + // } + // static T Read(EntryReader& aER) { + // /* Call `aER.ReadX(...)` function to deserialize and return a `T`, be + // sure to read exactly `Bytes(returned value)`! */ + // } + template <typename T> + struct Deserializer; + + ProfileBufferEntryReader() = default; + + // Reader over one Span. + ProfileBufferEntryReader(SpanOfConstBytes aSpan, + ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) + : mCurrentSpan(aSpan), + mNextSpanOrEmpty(aSpan.Last(0)), + mCurrentBlockIndex(aCurrentBlockIndex), + mNextBlockIndex(aNextBlockIndex) { + // 2nd internal Span points at the end of the 1st internal Span, to enforce + // invariants. + CheckInvariants(); + } + + // Reader over two Spans, the second one must not be empty. + ProfileBufferEntryReader(SpanOfConstBytes aSpanHead, + SpanOfConstBytes aSpanTail, + ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) + : mCurrentSpan(aSpanHead), + mNextSpanOrEmpty(aSpanTail), + mCurrentBlockIndex(aCurrentBlockIndex), + mNextBlockIndex(aNextBlockIndex) { + MOZ_RELEASE_ASSERT(!mNextSpanOrEmpty.IsEmpty()); + if (MOZ_UNLIKELY(mCurrentSpan.IsEmpty())) { + // First span is already empty, skip it. + mCurrentSpan = mNextSpanOrEmpty; + mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); + } + CheckInvariants(); + } + + // Allow copying, which is needed when used as an iterator in some std + // functions (e.g., string assignment), and to occasionally backtrack. + // Be aware that the main profile buffer APIs give a reference to an entry + // reader, and expect that reader to advance to the end of the entry, so don't + // just advance copies! + ProfileBufferEntryReader(const ProfileBufferEntryReader&) = default; + ProfileBufferEntryReader& operator=(const ProfileBufferEntryReader&) = + default; + + // Don't =default moving, as it doesn't bring any benefit in this class. + + [[nodiscard]] Length RemainingBytes() const { + return mCurrentSpan.LengthBytes() + mNextSpanOrEmpty.LengthBytes(); + } + + void SetRemainingBytes(Length aBytes) { + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + if (aBytes <= mCurrentSpan.LengthBytes()) { + mCurrentSpan = mCurrentSpan.First(aBytes); + mNextSpanOrEmpty = mCurrentSpan.Last(0); + } else { + mNextSpanOrEmpty = + mNextSpanOrEmpty.First(aBytes - mCurrentSpan.LengthBytes()); + } + } + + [[nodiscard]] ProfileBufferBlockIndex CurrentBlockIndex() const { + return mCurrentBlockIndex; + } + + [[nodiscard]] ProfileBufferBlockIndex NextBlockIndex() const { + return mNextBlockIndex; + } + + // Create a reader of size zero, pointing at aOffset past the current position + // of this Reader, so it can be used as end iterator. + [[nodiscard]] ProfileBufferEntryReader EmptyIteratorAtOffset( + Length aOffset) const { + MOZ_RELEASE_ASSERT(aOffset <= RemainingBytes()); + if (MOZ_LIKELY(aOffset < mCurrentSpan.LengthBytes())) { + // aOffset is before the end of mCurrentSpan. + return ProfileBufferEntryReader(mCurrentSpan.Subspan(aOffset, 0), + mCurrentBlockIndex, mNextBlockIndex); + } + // aOffset is right at the end of mCurrentSpan, or inside mNextSpanOrEmpty. + return ProfileBufferEntryReader( + mNextSpanOrEmpty.Subspan(aOffset - mCurrentSpan.LengthBytes(), 0), + mCurrentBlockIndex, mNextBlockIndex); + } + + // Be like a limited input iterator, with only `*`, prefix-`++`, `==`, `!=`. + // 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 = std::make_signed_t<Length>; + using value_type = Byte; + using pointer = const Byte*; + using reference = const Byte&; + using iterator_category = std::input_iterator_tag; + + [[nodiscard]] const Byte& operator*() { + // Assume the caller will read from the returned reference (and not just + // take the address). + MOZ_RELEASE_ASSERT(mCurrentSpan.LengthBytes() >= 1); + return *(mCurrentSpan.Elements()); + } + + ProfileBufferEntryReader& operator++() { + MOZ_RELEASE_ASSERT(mCurrentSpan.LengthBytes() >= 1); + if (MOZ_LIKELY(mCurrentSpan.LengthBytes() > 1)) { + // More than 1 byte left in mCurrentSpan, just eat it. + mCurrentSpan = mCurrentSpan.From(1); + } else { + // mCurrentSpan will be empty, move mNextSpanOrEmpty to mCurrentSpan. + mCurrentSpan = mNextSpanOrEmpty; + mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); + } + CheckInvariants(); + return *this; + } + + ProfileBufferEntryReader& operator+=(Length aBytes) { + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { + // All bytes are in mCurrentSpan. + // Update mCurrentSpan past the read bytes. + mCurrentSpan = mCurrentSpan.From(aBytes); + if (mCurrentSpan.IsEmpty() && !mNextSpanOrEmpty.IsEmpty()) { + // Don't leave mCurrentSpan empty, move non-empty mNextSpanOrEmpty into + // mCurrentSpan. + mCurrentSpan = mNextSpanOrEmpty; + mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); + } + } else { + // mCurrentSpan does not hold enough bytes. + // This should only happen at most once: Only for double spans, and when + // data crosses the gap. + const Length tail = + aBytes - static_cast<Length>(mCurrentSpan.LengthBytes()); + // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call + // will go back to the true case above. + mCurrentSpan = mNextSpanOrEmpty.From(tail); + mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); + } + CheckInvariants(); + return *this; + } + + [[nodiscard]] bool operator==(const ProfileBufferEntryReader& aOther) const { + return mCurrentSpan.Elements() == aOther.mCurrentSpan.Elements(); + } + [[nodiscard]] bool operator!=(const ProfileBufferEntryReader& aOther) const { + return mCurrentSpan.Elements() != aOther.mCurrentSpan.Elements(); + } + + // Read an unsigned LEB128 number and move iterator ahead. + template <typename T> + [[nodiscard]] T ReadULEB128() { + return ::mozilla::ReadULEB128<T>(*this); + } + + // This struct points at a number of bytes through either one span, or two + // separate spans (in the rare cases when it is split between two chunks). + // So the possibilities are: + // - Totally empty: { [] [] } + // - First span is not empty: { [content] [] } (Most common case.) + // - Both spans are not empty: { [cont] [ent] } + // But something like { [] [content] } is not possible. + // + // Recommended usage patterns: + // - Call a utility function like `CopyBytesTo` if you always need to copy the + // data to an outside buffer, e.g., to deserialize an aligned object. + // - Access both spans one after the other; Note that the second one may be + // empty; and the fist could be empty as well if there is no data at all. + // - Check is the second span is empty, in which case you only need to read + // the first one; and since its part of a chunk, it may be directly passed + // as an unaligned pointer or reference, thereby saving one copy. But + // remember to always handle the double-span case as well. + // + // Reminder: An empty span still has a non-null pointer, so it's safe to use + // with functions like memcpy. + struct DoubleSpanOfConstBytes { + SpanOfConstBytes mFirstOrOnly; + SpanOfConstBytes mSecondOrEmpty; + + void CheckInvariants() const { + MOZ_ASSERT(mFirstOrOnly.IsEmpty() ? mSecondOrEmpty.IsEmpty() : true, + "mSecondOrEmpty should not be the only span to contain data"); + } + + DoubleSpanOfConstBytes() : mFirstOrOnly(), mSecondOrEmpty() { + CheckInvariants(); + } + + DoubleSpanOfConstBytes(const Byte* aOnlyPointer, size_t aOnlyLength) + : mFirstOrOnly(aOnlyPointer, aOnlyLength), mSecondOrEmpty() { + CheckInvariants(); + } + + DoubleSpanOfConstBytes(const Byte* aFirstPointer, size_t aFirstLength, + const Byte* aSecondPointer, size_t aSecondLength) + : mFirstOrOnly(aFirstPointer, aFirstLength), + mSecondOrEmpty(aSecondPointer, aSecondLength) { + CheckInvariants(); + } + + // Is there no data at all? + [[nodiscard]] bool IsEmpty() const { + // We only need to check the first span, because if it's empty, the second + // one must be empty as well. + return mFirstOrOnly.IsEmpty(); + } + + // Total length (in bytes) pointed at by both spans. + [[nodiscard]] size_t LengthBytes() const { + return mFirstOrOnly.LengthBytes() + mSecondOrEmpty.LengthBytes(); + } + + // Utility functions to copy all `LengthBytes()` to a given buffer. + void CopyBytesTo(void* aDest) const { + memcpy(aDest, mFirstOrOnly.Elements(), mFirstOrOnly.LengthBytes()); + if (MOZ_UNLIKELY(!mSecondOrEmpty.IsEmpty())) { + memcpy(static_cast<Byte*>(aDest) + mFirstOrOnly.LengthBytes(), + mSecondOrEmpty.Elements(), mSecondOrEmpty.LengthBytes()); + } + } + + // If the second span is empty, only the first span may point at data. + [[nodiscard]] bool IsSingleSpan() const { return mSecondOrEmpty.IsEmpty(); } + }; + + // Get Span(s) to a sequence of bytes, see `DoubleSpanOfConstBytes` for usage. + // Note that the reader location is *not* updated, do `+=` on it afterwards. + [[nodiscard]] DoubleSpanOfConstBytes PeekSpans(Length aBytes) const { + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { + // All `aBytes` are in the current chunk, only one span is needed. + return DoubleSpanOfConstBytes{mCurrentSpan.Elements(), aBytes}; + } + // Otherwise the first span covers then end of the current chunk, and the + // second span starts in the next chunk. + return DoubleSpanOfConstBytes{ + mCurrentSpan.Elements(), mCurrentSpan.LengthBytes(), + mNextSpanOrEmpty.Elements(), aBytes - mCurrentSpan.LengthBytes()}; + } + + // Get Span(s) to a sequence of bytes, see `DoubleSpanOfConstBytes` for usage, + // and move the reader forward. + [[nodiscard]] DoubleSpanOfConstBytes ReadSpans(Length aBytes) { + DoubleSpanOfConstBytes spans = PeekSpans(aBytes); + (*this) += aBytes; + return spans; + } + + // Read a sequence of bytes, like memcpy. + void ReadBytes(void* aDest, Length aBytes) { + DoubleSpanOfConstBytes spans = ReadSpans(aBytes); + MOZ_ASSERT(spans.LengthBytes() == aBytes); + spans.CopyBytesTo(aDest); + } + + template <typename T> + void ReadIntoObject(T& aObject) { + Deserializer<T>::ReadInto(*this, aObject); + } + + // Read into one or more objects, sequentially. + // `EntryReader::ReadIntoObjects()` with nothing is implicitly allowed, this + // could be useful for generic programming. + template <typename... Ts> + void ReadIntoObjects(Ts&... aTs) { + (ReadIntoObject(aTs), ...); + } + + // Read data as an object and move iterator ahead. + template <typename T> + [[nodiscard]] T ReadObject() { + T ob = Deserializer<T>::Read(*this); + return ob; + } + + private: + friend class ProfileBufferEntryWriter; + + // Invariants: + // - mCurrentSpan cannot be empty unless mNextSpanOrEmpty is also empty. So + // mCurrentSpan always points at the next byte to read or the end. + // - If mNextSpanOrEmpty is empty, it points at the end of mCurrentSpan. So + // when reaching the end of mCurrentSpan, we can blindly move + // mNextSpanOrEmpty to mCurrentSpan and keep the invariants. + SpanOfConstBytes mCurrentSpan; + SpanOfConstBytes mNextSpanOrEmpty; + ProfileBufferBlockIndex mCurrentBlockIndex; + ProfileBufferBlockIndex mNextBlockIndex; + + void CheckInvariants() const { + MOZ_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); + MOZ_ASSERT(!mNextSpanOrEmpty.IsEmpty() || + (mNextSpanOrEmpty == mCurrentSpan.Last(0))); + } +}; + +// Iterator-like class used to write into an entry. +// An entry may be split in two memory segments (e.g., the ends of a ring +// buffer, or two chunks of a chunked buffer); it doesn't deal with this +// underlying buffer, but only with one or two spans pointing at the space +// reserved for the entry. +class ProfileBufferEntryWriter { + public: + using Byte = uint8_t; + using Length = uint32_t; + + using SpanOfBytes = Span<Byte>; + + // Class to be specialized for types to be written in an entry. + // See common specializations at the bottom of this header. + // The following static functions must be provided: + // static Length Bytes(const T& aT) { + // /* Return number of bytes that will be written. */ + // } + // static void Write(ProfileBufferEntryWriter& aEW, + // const T& aT) { + // /* Call `aEW.WriteX(...)` functions to serialize aT, be sure to write + // exactly `Bytes(aT)` bytes! */ + // } + template <typename T> + struct Serializer; + + ProfileBufferEntryWriter() = default; + + ProfileBufferEntryWriter(SpanOfBytes aSpan, + ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) + : mCurrentSpan(aSpan), + mCurrentBlockIndex(aCurrentBlockIndex), + mNextBlockIndex(aNextBlockIndex) {} + + ProfileBufferEntryWriter(SpanOfBytes aSpanHead, SpanOfBytes aSpanTail, + ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) + : mCurrentSpan(aSpanHead), + mNextSpanOrEmpty(aSpanTail), + mCurrentBlockIndex(aCurrentBlockIndex), + mNextBlockIndex(aNextBlockIndex) { + // Either: + // - mCurrentSpan is not empty, OR + // - mNextSpanOrEmpty is empty if mNextSpanOrEmpty is empty as well. + MOZ_RELEASE_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); + } + + // Disable copying and moving, so we can't have multiple writing heads. + ProfileBufferEntryWriter(const ProfileBufferEntryWriter&) = delete; + ProfileBufferEntryWriter& operator=(const ProfileBufferEntryWriter&) = delete; + ProfileBufferEntryWriter(ProfileBufferEntryWriter&&) = delete; + ProfileBufferEntryWriter& operator=(ProfileBufferEntryWriter&&) = delete; + + void Set() { + mCurrentSpan = SpanOfBytes{}; + mNextSpanOrEmpty = SpanOfBytes{}; + mCurrentBlockIndex = nullptr; + mNextBlockIndex = nullptr; + } + + void Set(SpanOfBytes aSpan, ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) { + mCurrentSpan = aSpan; + mNextSpanOrEmpty = SpanOfBytes{}; + mCurrentBlockIndex = aCurrentBlockIndex; + mNextBlockIndex = aNextBlockIndex; + } + + void Set(SpanOfBytes aSpan0, SpanOfBytes aSpan1, + ProfileBufferBlockIndex aCurrentBlockIndex, + ProfileBufferBlockIndex aNextBlockIndex) { + mCurrentSpan = aSpan0; + mNextSpanOrEmpty = aSpan1; + mCurrentBlockIndex = aCurrentBlockIndex; + mNextBlockIndex = aNextBlockIndex; + // Either: + // - mCurrentSpan is not empty, OR + // - mNextSpanOrEmpty is empty if mNextSpanOrEmpty is empty as well. + MOZ_RELEASE_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); + } + + [[nodiscard]] Length RemainingBytes() const { + return mCurrentSpan.LengthBytes() + mNextSpanOrEmpty.LengthBytes(); + } + + [[nodiscard]] ProfileBufferBlockIndex CurrentBlockIndex() const { + return mCurrentBlockIndex; + } + + [[nodiscard]] ProfileBufferBlockIndex NextBlockIndex() const { + return mNextBlockIndex; + } + + // Be like a limited output iterator, with only `*` and prefix-`++`. + // These definitions are expected by std functions, to recognize this as an + // iterator. See https://en.cppreference.com/w/cpp/iterator/iterator_traits + using value_type = Byte; + using pointer = Byte*; + using reference = Byte&; + using iterator_category = std::output_iterator_tag; + + [[nodiscard]] Byte& operator*() { + MOZ_RELEASE_ASSERT(RemainingBytes() >= 1); + return *( + (MOZ_LIKELY(!mCurrentSpan.IsEmpty()) ? mCurrentSpan : mNextSpanOrEmpty) + .Elements()); + } + + ProfileBufferEntryWriter& operator++() { + if (MOZ_LIKELY(mCurrentSpan.LengthBytes() >= 1)) { + // There is at least 1 byte in mCurrentSpan, eat it. + mCurrentSpan = mCurrentSpan.From(1); + } else { + // mCurrentSpan is empty, move mNextSpanOrEmpty (past the first byte) to + // mCurrentSpan. + MOZ_RELEASE_ASSERT(mNextSpanOrEmpty.LengthBytes() >= 1); + mCurrentSpan = mNextSpanOrEmpty.From(1); + mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); + } + return *this; + } + + ProfileBufferEntryWriter& operator+=(Length aBytes) { + // Note: This is a rare operation. The code below is a copy of `WriteBytes` + // but without the `memcpy`s. + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { + // Data fits in mCurrentSpan. + // Update mCurrentSpan. It may become empty, so in case of a double span, + // the next call will go to the false case below. + mCurrentSpan = mCurrentSpan.From(aBytes); + } else { + // Data does not fully fit in mCurrentSpan. + // This should only happen at most once: Only for double spans, and when + // data crosses the gap or starts there. + const Length tail = + aBytes - static_cast<Length>(mCurrentSpan.LengthBytes()); + // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call + // will go back to the true case above. + mCurrentSpan = mNextSpanOrEmpty.From(tail); + mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); + } + return *this; + } + + // Number of bytes needed to represent `aValue` in unsigned LEB128. + template <typename T> + [[nodiscard]] static unsigned ULEB128Size(T aValue) { + return ::mozilla::ULEB128Size(aValue); + } + + // Write number as unsigned LEB128 and move iterator ahead. + template <typename T> + void WriteULEB128(T aValue) { + ::mozilla::WriteULEB128(aValue, *this); + } + + // Number of bytes needed to serialize objects. + template <typename... Ts> + [[nodiscard]] static Length SumBytes(const Ts&... aTs) { + return (0 + ... + Serializer<Ts>::Bytes(aTs)); + } + + // Write a sequence of bytes, like memcpy. + void WriteBytes(const void* aSrc, Length aBytes) { + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { + // Data fits in mCurrentSpan. + memcpy(mCurrentSpan.Elements(), aSrc, aBytes); + // Update mCurrentSpan. It may become empty, so in case of a double span, + // the next call will go to the false case below. + mCurrentSpan = mCurrentSpan.From(aBytes); + } else { + // Data does not fully fit in mCurrentSpan. + // This should only happen at most once: Only for double spans, and when + // data crosses the gap or starts there. + // Split data between the end of mCurrentSpan and the beginning of + // mNextSpanOrEmpty. (mCurrentSpan could be empty, it's ok to do a memcpy + // because Span::Elements() is never null.) + memcpy(mCurrentSpan.Elements(), aSrc, mCurrentSpan.LengthBytes()); + const Length tail = + aBytes - static_cast<Length>(mCurrentSpan.LengthBytes()); + memcpy(mNextSpanOrEmpty.Elements(), + reinterpret_cast<const Byte*>(aSrc) + mCurrentSpan.LengthBytes(), + tail); + // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call + // will go back to the true case above. + mCurrentSpan = mNextSpanOrEmpty.From(tail); + mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); + } + } + + void WriteFromReader(ProfileBufferEntryReader& aReader, Length aBytes) { + MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); + MOZ_RELEASE_ASSERT(aBytes <= aReader.RemainingBytes()); + Length read0 = std::min( + aBytes, static_cast<Length>(aReader.mCurrentSpan.LengthBytes())); + if (read0 != 0) { + WriteBytes(aReader.mCurrentSpan.Elements(), read0); + } + Length read1 = aBytes - read0; + if (read1 != 0) { + WriteBytes(aReader.mNextSpanOrEmpty.Elements(), read1); + } + aReader += aBytes; + } + + // Write a single object by using the appropriate Serializer. + template <typename T> + void WriteObject(const T& aObject) { + Serializer<T>::Write(*this, aObject); + } + + // Write one or more objects, sequentially. + // Allow `EntryWrite::WriteObjects()` with nothing, this could be useful + // for generic programming. + template <typename... Ts> + void WriteObjects(const Ts&... aTs) { + (WriteObject(aTs), ...); + } + + private: + // The two spans covering the memory still to be written. + SpanOfBytes mCurrentSpan; + SpanOfBytes mNextSpanOrEmpty; + ProfileBufferBlockIndex mCurrentBlockIndex; + ProfileBufferBlockIndex mNextBlockIndex; +}; + +// ============================================================================ +// Serializer and Deserializer ready-to-use specializations. + +// ---------------------------------------------------------------------------- +// Trivially-copyable types (default) + +// The default implementation works for all trivially-copyable types (e.g., +// PODs). +// +// Usage: `aEW.WriteObject(123);`. +// +// Raw pointers, though trivially-copyable, are explictly forbidden when writing +// (to avoid unexpected leaks/UAFs), instead use one of +// `WrapProfileBufferLiteralCStringPointer`, `WrapProfileBufferUnownedCString`, +// or `WrapProfileBufferRawPointer` as needed. +template <typename T> +struct ProfileBufferEntryWriter::Serializer { + static_assert(std::is_trivially_copyable_v<T>, + "Serializer only works with trivially-copyable types by " + "default, use/add specialization for other types."); + + static constexpr Length Bytes(const T&) { return sizeof(T); } + + static void Write(ProfileBufferEntryWriter& aEW, const T& aT) { + static_assert(!std::is_pointer<T>::value, + "Serializer won't write raw pointers by default, use " + "WrapProfileBufferRawPointer or other."); + aEW.WriteBytes(&aT, sizeof(T)); + } +}; + +// Usage: `aER.ReadObject<int>();` or `int x; aER.ReadIntoObject(x);`. +template <typename T> +struct ProfileBufferEntryReader::Deserializer { + static_assert(std::is_trivially_copyable_v<T>, + "Deserializer only works with trivially-copyable types by " + "default, use/add specialization for other types."); + + static void ReadInto(ProfileBufferEntryReader& aER, T& aT) { + aER.ReadBytes(&aT, sizeof(T)); + } + + static T Read(ProfileBufferEntryReader& aER) { + // Note that this creates a default `T` first, and then overwrites it with + // bytes from the buffer. Trivially-copyable types support this without UB. + T ob; + ReadInto(aER, ob); + return ob; + } +}; + +// ---------------------------------------------------------------------------- +// Strip const/volatile/reference from types. + +// Automatically strip `const`. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<const T> + : public ProfileBufferEntryWriter::Serializer<T> {}; + +template <typename T> +struct ProfileBufferEntryReader::Deserializer<const T> + : public ProfileBufferEntryReader::Deserializer<T> {}; + +// Automatically strip `volatile`. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<volatile T> + : public ProfileBufferEntryWriter::Serializer<T> {}; + +template <typename T> +struct ProfileBufferEntryReader::Deserializer<volatile T> + : public ProfileBufferEntryReader::Deserializer<T> {}; + +// Automatically strip `lvalue-reference`. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<T&> + : public ProfileBufferEntryWriter::Serializer<T> {}; + +template <typename T> +struct ProfileBufferEntryReader::Deserializer<T&> + : public ProfileBufferEntryReader::Deserializer<T> {}; + +// Automatically strip `rvalue-reference`. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<T&&> + : public ProfileBufferEntryWriter::Serializer<T> {}; + +template <typename T> +struct ProfileBufferEntryReader::Deserializer<T&&> + : public ProfileBufferEntryReader::Deserializer<T> {}; + +// ---------------------------------------------------------------------------- +// ProfileBufferBlockIndex + +// ProfileBufferBlockIndex, serialized as the underlying value. +template <> +struct ProfileBufferEntryWriter::Serializer<ProfileBufferBlockIndex> { + static constexpr Length Bytes(const ProfileBufferBlockIndex& aBlockIndex) { + return sizeof(ProfileBufferBlockIndex); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const ProfileBufferBlockIndex& aBlockIndex) { + aEW.WriteBytes(&aBlockIndex, sizeof(aBlockIndex)); + } +}; + +template <> +struct ProfileBufferEntryReader::Deserializer<ProfileBufferBlockIndex> { + static void ReadInto(ProfileBufferEntryReader& aER, + ProfileBufferBlockIndex& aBlockIndex) { + aER.ReadBytes(&aBlockIndex, sizeof(aBlockIndex)); + } + + static ProfileBufferBlockIndex Read(ProfileBufferEntryReader& aER) { + ProfileBufferBlockIndex blockIndex; + ReadInto(aER, blockIndex); + return blockIndex; + } +}; + +// ---------------------------------------------------------------------------- +// Literal C string pointer + +// Wrapper around a pointer to a literal C string. +template <size_t NonTerminalCharacters> +struct ProfileBufferLiteralCStringPointer { + const char* mCString; +}; + +// Wrap a pointer to a literal C string. +template <size_t CharactersIncludingTerminal> +ProfileBufferLiteralCStringPointer<CharactersIncludingTerminal - 1> +WrapProfileBufferLiteralCStringPointer( + const char (&aCString)[CharactersIncludingTerminal]) { + return {aCString}; +} + +// Literal C strings, serialized as the raw pointer because it is unique and +// valid for the whole program lifetime. +// +// Usage: `aEW.WriteObject(WrapProfileBufferLiteralCStringPointer("hi"));`. +// +// No deserializer is provided for this type, instead it must be deserialized as +// a raw pointer: `aER.ReadObject<const char*>();` +template <size_t CharactersIncludingTerminal> +struct ProfileBufferEntryReader::Deserializer< + ProfileBufferLiteralCStringPointer<CharactersIncludingTerminal>> { + static constexpr Length Bytes( + const ProfileBufferLiteralCStringPointer<CharactersIncludingTerminal>&) { + // We're only storing a pointer, its size is independent from the pointer + // value. + return sizeof(const char*); + } + + static void Write( + ProfileBufferEntryWriter& aEW, + const ProfileBufferLiteralCStringPointer<CharactersIncludingTerminal>& + aWrapper) { + // Write the pointer *value*, not the string contents. + aEW.WriteBytes(aWrapper.mCString, sizeof(aWrapper.mCString)); + } +}; + +// ---------------------------------------------------------------------------- +// C string contents + +// Wrapper around a pointer to a C string whose contents will be serialized. +struct ProfileBufferUnownedCString { + const char* mCString; +}; + +// Wrap a pointer to a C string whose contents will be serialized. +inline ProfileBufferUnownedCString WrapProfileBufferUnownedCString( + const char* aCString) { + return {aCString}; +} + +// The contents of a (probably) unowned C string are serialized as the number of +// characters (encoded as ULEB128) and all the characters in the string. The +// terminal '\0' is omitted. +// +// Usage: `aEW.WriteObject(WrapProfileBufferUnownedCString(str.c_str()))`. +// +// No deserializer is provided for this pointer type, instead it must be +// deserialized as one of the other string types that manages its contents, +// e.g.: `aER.ReadObject<std::string>();` +template <> +struct ProfileBufferEntryWriter::Serializer<ProfileBufferUnownedCString> { + static Length Bytes(const ProfileBufferUnownedCString& aS) { + const auto len = strlen(aS.mCString); + return ULEB128Size(len) + len; + } + + static void Write(ProfileBufferEntryWriter& aEW, + const ProfileBufferUnownedCString& aS) { + const auto len = strlen(aS.mCString); + aEW.WriteULEB128(len); + aEW.WriteBytes(aS.mCString, len); + } +}; + +// ---------------------------------------------------------------------------- +// Raw pointers + +// Wrapper around a pointer to be serialized as the raw pointer value. +template <typename T> +struct ProfileBufferRawPointer { + T* mRawPointer; +}; + +// Wrap a pointer to be serialized as the raw pointer value. +template <typename T> +ProfileBufferRawPointer<T> WrapProfileBufferRawPointer(T* aRawPointer) { + return {aRawPointer}; +} + +// Raw pointers are serialized as the raw pointer value. +// +// Usage: `aEW.WriteObject(WrapProfileBufferRawPointer(ptr));` +// +// The wrapper is compulsory when writing pointers (to avoid unexpected +// leaks/UAFs), but reading can be done straight into a raw pointer object, +// e.g.: `aER.ReadObject<Foo*>;`. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<ProfileBufferRawPointer<T>> { + template <typename U> + static constexpr Length Bytes(const U&) { + return sizeof(T*); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const ProfileBufferRawPointer<T>& aWrapper) { + aEW.WriteBytes(&aWrapper.mRawPointer, sizeof(aWrapper.mRawPointer)); + } +}; + +// Usage: `aER.ReadObject<Foo*>;` or `Foo* p; aER.ReadIntoObject(p);`, no +// wrapper necessary. +template <typename T> +struct ProfileBufferEntryReader::Deserializer<ProfileBufferRawPointer<T>> { + static void ReadInto(ProfileBufferEntryReader& aER, + ProfileBufferRawPointer<T>& aPtr) { + aER.ReadBytes(&aPtr.mRawPointer, sizeof(aPtr)); + } + + static ProfileBufferRawPointer<T> Read(ProfileBufferEntryReader& aER) { + ProfileBufferRawPointer<T> rawPointer; + ReadInto(aER, rawPointer); + return rawPointer; + } +}; + +// ---------------------------------------------------------------------------- +// std::string contents + +// std::string contents are serialized as the number of characters (encoded as +// ULEB128) and all the characters in the string. The terminal '\0' is omitted. +// +// Usage: `std::string s = ...; aEW.WriteObject(s);` +template <typename CHAR> +struct ProfileBufferEntryWriter::Serializer<std::basic_string<CHAR>> { + static Length Bytes(const std::basic_string<CHAR>& aS) { + const Length len = static_cast<Length>(aS.length()); + return ULEB128Size(len) + len; + } + + static void Write(ProfileBufferEntryWriter& aEW, + const std::basic_string<CHAR>& aS) { + const Length len = static_cast<Length>(aS.length()); + aEW.WriteULEB128(len); + aEW.WriteBytes(aS.c_str(), len * sizeof(CHAR)); + } +}; + +// Usage: `std::string s = aEW.ReadObject<std::string>(s);` or +// `std::string s; aER.ReadIntoObject(s);` +template <typename CHAR> +struct ProfileBufferEntryReader::Deserializer<std::basic_string<CHAR>> { + static void ReadCharsInto(ProfileBufferEntryReader& aER, + std::basic_string<CHAR>& aS, size_t aLength) { + // Assign to `aS` by using iterators. + // (`aER+0` so we get the same iterator type as `aER+len`.) + aS.assign(aER, aER.EmptyIteratorAtOffset(aLength)); + aER += aLength; + } + + static void ReadInto(ProfileBufferEntryReader& aER, + std::basic_string<CHAR>& aS) { + ReadCharsInto( + aER, aS, + aER.ReadULEB128<typename std::basic_string<CHAR>::size_type>()); + } + + static std::basic_string<CHAR> ReadChars(ProfileBufferEntryReader& aER, + size_t aLength) { + // Construct a string by using iterators. + // (`aER+0` so we get the same iterator type as `aER+len`.) + std::basic_string<CHAR> s(aER, aER.EmptyIteratorAtOffset(aLength)); + aER += aLength; + return s; + } + + static std::basic_string<CHAR> Read(ProfileBufferEntryReader& aER) { + return ReadChars( + aER, aER.ReadULEB128<typename std::basic_string<CHAR>::size_type>()); + } +}; + +// ---------------------------------------------------------------------------- +// mozilla::UniqueFreePtr<CHAR> + +// UniqueFreePtr<CHAR>, which points at a string allocated with `malloc` +// (typically generated by `strdup()`), is serialized as the number of +// *bytes* (encoded as ULEB128) and all the characters in the string. The +// null terminator is omitted. +// `CHAR` can be any type that has a specialization for +// `std::char_traits<CHAR>::length(const CHAR*)`. +// +// Note: A nullptr pointer will be serialized like an empty string, so when +// deserializing it will result in an allocated buffer only containing a +// single null terminator. +template <typename CHAR> +struct ProfileBufferEntryWriter::Serializer<UniqueFreePtr<CHAR>> { + static Length Bytes(const UniqueFreePtr<CHAR>& aS) { + if (!aS) { + // Null pointer, store it as if it was an empty string (so: 0 bytes). + return ULEB128Size(0u); + } + // Note that we store the size in *bytes*, not in number of characters. + const auto bytes = std::char_traits<CHAR>::length(aS.get()) * sizeof(CHAR); + return ULEB128Size(bytes) + bytes; + } + + static void Write(ProfileBufferEntryWriter& aEW, + const UniqueFreePtr<CHAR>& aS) { + if (!aS) { + // Null pointer, store it as if it was an empty string (so we write a + // length of 0 bytes). + aEW.WriteULEB128(0u); + return; + } + // Note that we store the size in *bytes*, not in number of characters. + const auto bytes = std::char_traits<CHAR>::length(aS.get()) * sizeof(CHAR); + aEW.WriteULEB128(bytes); + aEW.WriteBytes(aS.get(), bytes); + } +}; + +template <typename CHAR> +struct ProfileBufferEntryReader::Deserializer<UniqueFreePtr<CHAR>> { + static void ReadInto(ProfileBufferEntryReader& aER, UniqueFreePtr<CHAR>& aS) { + aS = Read(aER); + } + + static UniqueFreePtr<CHAR> Read(ProfileBufferEntryReader& aER) { + // Read the number of *bytes* that follow. + const auto bytes = aER.ReadULEB128<size_t>(); + // We need a buffer of the non-const character type. + using NC_CHAR = std::remove_const_t<CHAR>; + // We allocate the required number of bytes, plus one extra character for + // the null terminator. + NC_CHAR* buffer = static_cast<NC_CHAR*>(malloc(bytes + sizeof(NC_CHAR))); + // Copy the characters into the buffer. + aER.ReadBytes(buffer, bytes); + // And append a null terminator. + buffer[bytes / sizeof(NC_CHAR)] = NC_CHAR(0); + return UniqueFreePtr<CHAR>(buffer); + } +}; + +// ---------------------------------------------------------------------------- +// std::tuple + +// std::tuple is serialized as a sequence of each recursively-serialized item. +// +// This is equivalent to manually serializing each item, so reading/writing +// tuples is equivalent to reading/writing their elements in order, e.g.: +// ``` +// std::tuple<int, std::string> is = ...; +// aEW.WriteObject(is); // Write the tuple, equivalent to: +// aEW.WriteObject(/* int */ std::get<0>(is), /* string */ std::get<1>(is)); +// ... +// // Reading back can be done directly into a tuple: +// auto is = aER.ReadObject<std::tuple<int, std::string>>(); +// // Or each item could be read separately: +// auto i = aER.ReadObject<int>(); auto s = aER.ReadObject<std::string>(); +// ``` +template <typename... Ts> +struct ProfileBufferEntryWriter::Serializer<std::tuple<Ts...>> { + private: + template <size_t... Is> + static Length TupleBytes(const std::tuple<Ts...>& aTuple, + std::index_sequence<Is...>) { + return (0 + ... + SumBytes(std::get<Is>(aTuple))); + } + + template <size_t... Is> + static void TupleWrite(ProfileBufferEntryWriter& aEW, + const std::tuple<Ts...>& aTuple, + std::index_sequence<Is...>) { + (aEW.WriteObject(std::get<Is>(aTuple)), ...); + } + + public: + static Length Bytes(const std::tuple<Ts...>& aTuple) { + // Generate a 0..N-1 index pack, we'll add the sizes of each item. + return TupleBytes(aTuple, std::index_sequence_for<Ts...>()); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const std::tuple<Ts...>& aTuple) { + // Generate a 0..N-1 index pack, we'll write each item. + TupleWrite(aEW, aTuple, std::index_sequence_for<Ts...>()); + } +}; + +template <typename... Ts> +struct ProfileBufferEntryReader::Deserializer<std::tuple<Ts...>> { + template <size_t I> + static void TupleIReadInto(ProfileBufferEntryReader& aER, + std::tuple<Ts...>& aTuple) { + aER.ReadIntoObject(std::get<I>(aTuple)); + } + + template <size_t... Is> + static void TupleReadInto(ProfileBufferEntryReader& aER, + std::tuple<Ts...>& aTuple, + std::index_sequence<Is...>) { + (TupleIReadInto<Is>(aER, aTuple), ...); + } + + static void ReadInto(ProfileBufferEntryReader& aER, + std::tuple<Ts...>& aTuple) { + TupleReadInto(aER, aTuple, std::index_sequence_for<Ts...>()); + } + + static std::tuple<Ts...> Read(ProfileBufferEntryReader& aER) { + // Note that this creates default `Ts` first, and then overwrites them. + std::tuple<Ts...> ob; + ReadInto(aER, ob); + return ob; + } +}; +// ---------------------------------------------------------------------------- +// mozilla::Span + +// Span. All elements are serialized in sequence. +// The caller is assumed to know the number of elements (they may manually +// write&read it before the span if needed). +// Similar to tuples, reading/writing spans is equivalent to reading/writing +// their elements in order. +template <class T, size_t N> +struct ProfileBufferEntryWriter::Serializer<Span<T, N>> { + static Length Bytes(const Span<T, N>& aSpan) { + Length bytes = 0; + for (const T& element : aSpan) { + bytes += SumBytes(element); + } + return bytes; + } + + static void Write(ProfileBufferEntryWriter& aEW, const Span<T, N>& aSpan) { + for (const T& element : aSpan) { + aEW.WriteObject(element); + } + } +}; + +template <class T, size_t N> +struct ProfileBufferEntryReader::Deserializer<Span<T, N>> { + // Read elements back into span pointing at a pre-allocated buffer. + static void ReadInto(ProfileBufferEntryReader& aER, Span<T, N>& aSpan) { + for (T& element : aSpan) { + aER.ReadIntoObject(element); + } + } + + // A Span does not own its data, this would probably leak so we forbid this. + static Span<T, N> Read(ProfileBufferEntryReader& aER) = delete; +}; + +// ---------------------------------------------------------------------------- +// mozilla::Maybe + +// Maybe<T> is serialized as one byte containing either 'm' (Nothing), +// or 'M' followed by the recursively-serialized `T` object. +template <typename T> +struct ProfileBufferEntryWriter::Serializer<Maybe<T>> { + static Length Bytes(const Maybe<T>& aMaybe) { + // 1 byte to store nothing/something flag, then object size if present. + return aMaybe.isNothing() ? 1 : (1 + SumBytes(aMaybe.ref())); + } + + static void Write(ProfileBufferEntryWriter& aEW, const Maybe<T>& aMaybe) { + // 'm'/'M' is just an arbitrary 1-byte value to distinguish states. + if (aMaybe.isNothing()) { + aEW.WriteObject<char>('m'); + } else { + aEW.WriteObject<char>('M'); + // Use the Serializer for the contained type. + aEW.WriteObject(aMaybe.ref()); + } + } +}; + +template <typename T> +struct ProfileBufferEntryReader::Deserializer<Maybe<T>> { + static void ReadInto(ProfileBufferEntryReader& aER, Maybe<T>& aMaybe) { + char c = aER.ReadObject<char>(); + if (c == 'm') { + aMaybe.reset(); + } else { + MOZ_ASSERT(c == 'M'); + // If aMaybe is empty, create a default `T` first, to be overwritten. + // Otherwise we'll just overwrite whatever was already there. + if (aMaybe.isNothing()) { + aMaybe.emplace(); + } + // Use the Deserializer for the contained type. + aER.ReadIntoObject(aMaybe.ref()); + } + } + + static Maybe<T> Read(ProfileBufferEntryReader& aER) { + Maybe<T> maybe; + char c = aER.ReadObject<char>(); + MOZ_ASSERT(c == 'M' || c == 'm'); + if (c == 'M') { + // Note that this creates a default `T` inside the Maybe first, and then + // overwrites it. + maybe = Some(T{}); + // Use the Deserializer for the contained type. + aER.ReadIntoObject(maybe.ref()); + } + return maybe; + } +}; + +// ---------------------------------------------------------------------------- +// mozilla::Variant + +// Variant is serialized as the tag (0-based index of the stored type, encoded +// as ULEB128), and the recursively-serialized object. +template <typename... Ts> +struct ProfileBufferEntryWriter::Serializer<Variant<Ts...>> { + public: + static Length Bytes(const Variant<Ts...>& aVariantTs) { + return aVariantTs.match([](auto aIndex, const auto& aAlternative) { + return ULEB128Size(aIndex) + SumBytes(aAlternative); + }); + } + + static void Write(ProfileBufferEntryWriter& aEW, + const Variant<Ts...>& aVariantTs) { + aVariantTs.match([&aEW](auto aIndex, const auto& aAlternative) { + aEW.WriteULEB128(aIndex); + aEW.WriteObject(aAlternative); + }); + } +}; + +template <typename... Ts> +struct ProfileBufferEntryReader::Deserializer<Variant<Ts...>> { + private: + // Called from the fold expression in `VariantReadInto()`, only the selected + // variant will deserialize the object. + template <size_t I> + static void VariantIReadInto(ProfileBufferEntryReader& aER, + Variant<Ts...>& aVariantTs, unsigned aTag) { + if (I == aTag) { + // Ensure the variant contains the target type. Note that this may create + // a default object. + if (!aVariantTs.template is<I>()) { + aVariantTs = Variant<Ts...>(VariantIndex<I>{}); + } + aER.ReadIntoObject(aVariantTs.template as<I>()); + } + } + + template <size_t... Is> + static void VariantReadInto(ProfileBufferEntryReader& aER, + Variant<Ts...>& aVariantTs, + std::index_sequence<Is...>) { + unsigned tag = aER.ReadULEB128<unsigned>(); + (VariantIReadInto<Is>(aER, aVariantTs, tag), ...); + } + + public: + static void ReadInto(ProfileBufferEntryReader& aER, + Variant<Ts...>& aVariantTs) { + // Generate a 0..N-1 index pack, the selected variant will deserialize + // itself. + VariantReadInto(aER, aVariantTs, std::index_sequence_for<Ts...>()); + } + + static Variant<Ts...> Read(ProfileBufferEntryReader& aER) { + // Note that this creates a default `Variant` of the first type, and then + // overwrites it. Consider using `ReadInto` for more control if needed. + Variant<Ts...> variant(VariantIndex<0>{}); + ReadInto(aER, variant); + return variant; + } +}; + +} // namespace mozilla + +#endif // ProfileBufferEntrySerialization_h |