summaryrefslogtreecommitdiffstats
path: root/mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h')
-rw-r--r--mozglue/baseprofiler/public/ProfileBufferEntrySerialization.h1184
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