/* -*- 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 BaseProfilerMarkersDetail_h
#define BaseProfilerMarkersDetail_h

#ifndef BaseProfilerMarkers_h
#  error "This header should only be #included by BaseProfilerMarkers.h"
#endif

#include "mozilla/BaseProfilerMarkersPrerequisites.h"

//                        ~~ HERE BE DRAGONS ~~
//
// Everything below is internal implementation detail, you shouldn't need to
// look at it unless working on the profiler code.

#include "mozilla/BaseProfileJSONWriter.h"
#include "mozilla/ProfileBufferEntryKinds.h"

#include <limits>
#include <tuple>

namespace mozilla::baseprofiler {
// Implemented in platform.cpp
MFBT_API ProfileChunkedBuffer& profiler_get_core_buffer();
}  // namespace mozilla::baseprofiler

namespace mozilla::base_profiler_markers_detail {

struct Streaming {
  // A `MarkerDataDeserializer` is a free function that can read a serialized
  // payload from an `EntryReader` and streams it as JSON object properties.
  using MarkerDataDeserializer = void (*)(ProfileBufferEntryReader&,
                                          baseprofiler::SpliceableJSONWriter&);

  // A `MarkerTypeNameFunction` is a free function that returns the name of the
  // marker type.
  using MarkerTypeNameFunction = Span<const char> (*)();

  // A `MarkerSchemaFunction` is a free function that returns a
  // `MarkerSchema`, which contains all the information needed to stream
  // the display schema associated with a marker type.
  using MarkerSchemaFunction = MarkerSchema (*)();

  struct MarkerTypeFunctions {
    MarkerDataDeserializer mMarkerDataDeserializer = nullptr;
    MarkerTypeNameFunction mMarkerTypeNameFunction = nullptr;
    MarkerSchemaFunction mMarkerSchemaFunction = nullptr;
  };

  // A `DeserializerTag` will be added before the payload, to help select the
  // correct deserializer when reading back the payload.
  using DeserializerTag = uint8_t;

  // Store a deserializer (and other marker-type-specific functions) and get its
  // `DeserializerTag`.
  // This is intended to be only used once per deserializer when a new marker
  // type is used for the first time, so it should be called to initialize a
  // `static const` tag that will be re-used by all markers of the corresponding
  // payload type -- see use below.
  MFBT_API static DeserializerTag TagForMarkerTypeFunctions(
      MarkerDataDeserializer aDeserializer,
      MarkerTypeNameFunction aMarkerTypeNameFunction,
      MarkerSchemaFunction aMarkerSchemaFunction);

  // Get the `MarkerDataDeserializer` for a given `DeserializerTag`.
  MFBT_API static MarkerDataDeserializer DeserializerForTag(
      DeserializerTag aTag);

  // Retrieve all MarkerTypeFunctions's.
  MFBT_API static Span<const MarkerTypeFunctions> MarkerTypeFunctionsArray();
};

// This helper will examine a marker type's `StreamJSONMarkerData` function, see
// specialization below.
template <typename T>
struct StreamFunctionTypeHelper;

// Helper specialization that takes the expected
// `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function and
// provide information about the `...` parameters.
template <typename R, typename... As>
struct StreamFunctionTypeHelper<R(baseprofiler::SpliceableJSONWriter&, As...)> {
  constexpr static size_t scArity = sizeof...(As);
  using TupleType =
      std::tuple<std::remove_cv_t<std::remove_reference_t<As>>...>;

  // Serialization function that takes the exact same parameter types
  // (const-ref'd) as `StreamJSONMarkerData`. This has to be inside the helper
  // because only here can we access the raw parameter pack `As...`.
  // And because we're using the same argument types through
  // references-to-const, permitted implicit conversions can happen.
  static ProfileBufferBlockIndex Serialize(
      ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
      const MarkerCategory& aCategory, MarkerOptions&& aOptions,
      Streaming::DeserializerTag aDeserializerTag, const As&... aAs) {
    // Note that options are first after the entry kind, because they contain
    // the thread id, which is handled first to filter markers by threads.
    return aBuffer.PutObjects(ProfileBufferEntryKind::Marker, aOptions, aName,
                              aCategory, aDeserializerTag,
                              MarkerPayloadType::Cpp, aAs...);
  }
};

// Helper for a marker type.
// A marker type is defined in a `struct` with some expected static member
// functions. See example in BaseProfilerMarkers.h.
template <typename MarkerType>
struct MarkerTypeSerialization {
  // Definitions to access the expected
  // `StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter&, ...)` function
  // and its parameters.
  using StreamFunctionType =
      StreamFunctionTypeHelper<decltype(MarkerType::StreamJSONMarkerData)>;
  constexpr static size_t scStreamFunctionParameterCount =
      StreamFunctionType::scArity;
  using StreamFunctionUserParametersTuple =
      typename StreamFunctionType::TupleType;
  template <size_t i>
  using StreamFunctionParameter =
      std::tuple_element_t<i, StreamFunctionUserParametersTuple>;

  template <typename... Ts>
  static ProfileBufferBlockIndex Serialize(ProfileChunkedBuffer& aBuffer,
                                           const ProfilerString8View& aName,
                                           const MarkerCategory& aCategory,
                                           MarkerOptions&& aOptions,
                                           const Ts&... aTs) {
    static_assert(!std::is_same_v<MarkerType,
                                  ::mozilla::baseprofiler::markers::NoPayload>,
                  "NoPayload should have been handled in the caller.");
    // Register marker type functions, and get the tag for this deserializer.
    // Note that the tag is stored in a function-static object, and this
    // function is static in a templated struct, so there should only be one tag
    // per MarkerType.
    // Making the tag class-static may have been more efficient (to avoid a
    // thread-safe init check at every call), but random global static
    // initialization order would make it more complex to coordinate with
    // `Streaming::TagForMarkerTypeFunctions()`, and also would add a (small)
    // cost for everybody, even the majority of users not using the profiler.
    static const Streaming::DeserializerTag tag =
        Streaming::TagForMarkerTypeFunctions(Deserialize,
                                             MarkerType::MarkerTypeName,
                                             MarkerType::MarkerTypeDisplay);
    return StreamFunctionType::Serialize(aBuffer, aName, aCategory,
                                         std::move(aOptions), tag, aTs...);
  }

 private:
  // This templated function will recursively deserialize each argument expected
  // by `MarkerType::StreamJSONMarkerData()` on the stack, and call it at the
  // end. E.g., for `StreamJSONMarkerData(int, char)`:
  // - DeserializeArguments<0>(aER, aWriter) reads an int and calls:
  // - DeserializeArguments<1>(aER, aWriter, const int&) reads a char and calls:
  // - MarkerType::StreamJSONMarkerData(aWriter, const int&, const char&).
  // Prototyping on godbolt showed that clang and gcc can flatten these
  // recursive calls into one function with successive reads followed by the one
  // stream call; tested up to 40 arguments: https://godbolt.org/z/5KeeM4
  template <size_t i = 0, typename... Args>
  static void DeserializeArguments(ProfileBufferEntryReader& aEntryReader,
                                   baseprofiler::SpliceableJSONWriter& aWriter,
                                   const Args&... aArgs) {
    static_assert(sizeof...(Args) == i,
                  "We should have collected `i` arguments so far");
    if constexpr (i < scStreamFunctionParameterCount) {
      // Deserialize the i-th argument on this stack.
      auto argument = aEntryReader.ReadObject<StreamFunctionParameter<i>>();
      // Add our local argument to the next recursive call.
      DeserializeArguments<i + 1>(aEntryReader, aWriter, aArgs..., argument);
    } else {
      // We've read all the arguments, finally call the `StreamJSONMarkerData`
      // function, which should write the appropriate JSON elements for this
      // marker type. Note that the MarkerType-specific "type" element is
      // already written.
      MarkerType::StreamJSONMarkerData(aWriter, aArgs...);
    }
  }

 public:
  static void Deserialize(ProfileBufferEntryReader& aEntryReader,
                          baseprofiler::SpliceableJSONWriter& aWriter) {
    aWriter.StringProperty("type", MarkerType::MarkerTypeName());
    DeserializeArguments(aEntryReader, aWriter);
  }
};

template <>
struct MarkerTypeSerialization<::mozilla::baseprofiler::markers::NoPayload> {
  // Nothing! NoPayload has special handling avoiding payload work.
};

template <typename MarkerType, typename... Ts>
static ProfileBufferBlockIndex AddMarkerWithOptionalStackToBuffer(
    ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
    const MarkerCategory& aCategory, MarkerOptions&& aOptions,
    const Ts&... aTs) {
  if constexpr (std::is_same_v<MarkerType,
                               ::mozilla::baseprofiler::markers::NoPayload>) {
    static_assert(sizeof...(Ts) == 0,
                  "NoPayload does not accept any payload arguments.");
    // Special case for NoPayload where there is a stack or inner window id:
    // Because these options would be stored in the payload 'data' object, but
    // there is no such object for NoPayload, we convert the marker to another
    // type (without user fields in the 'data' object), so that the stack and/or
    // inner window id are not lost.
    // TODO: Remove this when bug 1646714 lands.
    if (aOptions.Stack().GetChunkedBuffer() ||
        !aOptions.InnerWindowId().IsUnspecified()) {
      struct NoPayloadUserData {
        static constexpr Span<const char> MarkerTypeName() {
          return MakeStringSpan("NoPayloadUserData");
        }
        static void StreamJSONMarkerData(
            baseprofiler::SpliceableJSONWriter& aWriter) {
          // No user payload.
        }
        static mozilla::MarkerSchema MarkerTypeDisplay() {
          using MS = mozilla::MarkerSchema;
          MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
          // No user data to display.
          return schema;
        }
      };
      return MarkerTypeSerialization<NoPayloadUserData>::Serialize(
          aBuffer, aName, aCategory, std::move(aOptions));
    }

    // Note that options are first after the entry kind, because they contain
    // the thread id, which is handled first to filter markers by threads.
    return aBuffer.PutObjects(
        ProfileBufferEntryKind::Marker, std::move(aOptions), aName, aCategory,
        base_profiler_markers_detail::Streaming::DeserializerTag(0));
  } else {
    return MarkerTypeSerialization<MarkerType>::Serialize(
        aBuffer, aName, aCategory, std::move(aOptions), aTs...);
  }
}

// Pointer to a function that can capture a backtrace into the provided
// `ProfileChunkedBuffer`, and returns true when successful.
using BacktraceCaptureFunction = bool (*)(ProfileChunkedBuffer&,
                                          StackCaptureOptions);

// Use a pre-allocated and cleared chunked buffer in the main thread's
// `AddMarkerToBuffer()`.
// Null if not the main thread, or if profilers are not active.
MFBT_API ProfileChunkedBuffer* GetClearedBufferForMainThreadAddMarker();
// Called by the profiler(s) when starting/stopping. Safe to nest.
MFBT_API void EnsureBufferForMainThreadAddMarker();
MFBT_API void ReleaseBufferForMainThreadAddMarker();

// Add a marker with the given name, options, and arguments to the given buffer.
// Because this may be called from either Base or Gecko Profiler functions, the
// appropriate backtrace-capturing function must also be provided.
template <typename MarkerType, typename... Ts>
ProfileBufferBlockIndex AddMarkerToBuffer(
    ProfileChunkedBuffer& aBuffer, const ProfilerString8View& aName,
    const MarkerCategory& aCategory, MarkerOptions&& aOptions,
    BacktraceCaptureFunction aBacktraceCaptureFunction, const Ts&... aTs) {
  if (aOptions.ThreadId().IsUnspecified()) {
    // If yet unspecified, set thread to this thread where the marker is added.
    aOptions.Set(MarkerThreadId::CurrentThread());
  }

  if (aOptions.IsTimingUnspecified()) {
    // If yet unspecified, set timing to this instant of adding the marker.
    aOptions.Set(MarkerTiming::InstantNow());
  }

  StackCaptureOptions captureOptions = aOptions.Stack().CaptureOptions();
  if (captureOptions != StackCaptureOptions::NoStack) {
    // A capture was requested, let's attempt to do it here&now. This avoids a
    // lot of allocations that would be necessary if capturing a backtrace
    // separately.
    // TODO reduce internal profiler stack levels, see bug 1659872.
    auto CaptureStackAndAddMarker = [&](ProfileChunkedBuffer& aChunkedBuffer) {
      aOptions.StackRef().UseRequestedBacktrace(
          aBacktraceCaptureFunction(aChunkedBuffer, captureOptions)
              ? &aChunkedBuffer
              : nullptr);
      // This call must be made from here, while chunkedBuffer is in scope.
      return AddMarkerWithOptionalStackToBuffer<MarkerType>(
          aBuffer, aName, aCategory, std::move(aOptions), aTs...);
    };

    if (ProfileChunkedBuffer* buffer = GetClearedBufferForMainThreadAddMarker();
        buffer) {
      // Use a pre-allocated buffer for the main thread (because it's the most
      // used thread, and most sensitive to overhead), so it's only allocated
      // once. It could be null if this is not the main thread, or no profilers
      // are currently active.
      return CaptureStackAndAddMarker(*buffer);
    }
    // TODO use a local on-stack byte buffer to remove last allocation.
    ProfileBufferChunkManagerSingle chunkManager(
        ProfileBufferChunkManager::scExpectedMaximumStackSize);
    ProfileChunkedBuffer chunkedBuffer(
        ProfileChunkedBuffer::ThreadSafety::WithoutMutex, chunkManager);
    return CaptureStackAndAddMarker(chunkedBuffer);
  }

  return AddMarkerWithOptionalStackToBuffer<MarkerType>(
      aBuffer, aName, aCategory, std::move(aOptions), aTs...);
}

// Assuming aEntryReader points right after the entry type (being Marker), this
// reads the remainder of the marker and outputs it.
// - GetWriterForThreadCallback, called first, after the thread id is read:
//     (ThreadId) -> SpliceableJSONWriter* or null
//   If null, nothing will be output, but aEntryReader will still be read fully.
// - StackCallback, only called if GetWriterForThreadCallback didn't return
//   null, and if the marker contains a stack:
//     (ProfileChunkedBuffer&) -> void
// - RustMarkerCallback, only called if GetWriterForThreadCallback didn't return
//   null, and if the marker contains a Rust payload:
//     (DeserializerTag) -> void
template <typename GetWriterForThreadCallback, typename StackCallback,
          typename RustMarkerCallback>
void DeserializeAfterKindAndStream(
    ProfileBufferEntryReader& aEntryReader,
    GetWriterForThreadCallback&& aGetWriterForThreadCallback,
    StackCallback&& aStackCallback, RustMarkerCallback&& aRustMarkerCallback) {
  // Each entry is made up of the following:
  //   ProfileBufferEntry::Kind::Marker, <- already read by caller
  //   options,                          <- next location in entries
  //   name,
  //   payload
  const MarkerOptions options = aEntryReader.ReadObject<MarkerOptions>();

  baseprofiler::SpliceableJSONWriter* writer =
      std::forward<GetWriterForThreadCallback>(aGetWriterForThreadCallback)(
          options.ThreadId().ThreadId());
  if (!writer) {
    // No writer associated with this thread id, drop it.
    aEntryReader.SetRemainingBytes(0);
    return;
  }

  // Write the information to JSON with the following schema:
  // [name, startTime, endTime, phase, category, data]
  writer->StartArrayElement();
  {
    writer->UniqueStringElement(aEntryReader.ReadObject<ProfilerString8View>());

    const double startTime = options.Timing().GetStartTime();
    writer->TimeDoubleMsElement(startTime);

    const double endTime = options.Timing().GetEndTime();
    writer->TimeDoubleMsElement(endTime);

    writer->IntElement(static_cast<int64_t>(options.Timing().MarkerPhase()));

    MarkerCategory category = aEntryReader.ReadObject<MarkerCategory>();
    writer->IntElement(static_cast<int64_t>(category.GetCategory()));

    if (const auto tag =
            aEntryReader.ReadObject<mozilla::base_profiler_markers_detail::
                                        Streaming::DeserializerTag>();
        tag != 0) {
      writer->StartObjectElement(JSONWriter::SingleLineStyle);
      {
        // Stream "common props".

        // TODO: Move this to top-level tuple, when frontend supports it.
        if (!options.InnerWindowId().IsUnspecified()) {
          // Here, we are converting uint64_t to double. Both Browsing Context
          // and Inner Window IDs are created using
          // `nsContentUtils::GenerateProcessSpecificId`, which is specifically
          // designed to only use 53 of the 64 bits to be lossless when passed
          // into and out of JS as a double.
          writer->DoubleProperty(
              "innerWindowID",
              static_cast<double>(options.InnerWindowId().Id()));
        }

        // TODO: Move this to top-level tuple, when frontend supports it.
        if (ProfileChunkedBuffer* chunkedBuffer =
                options.Stack().GetChunkedBuffer();
            chunkedBuffer) {
          writer->StartObjectProperty("stack");
          { std::forward<StackCallback>(aStackCallback)(*chunkedBuffer); }
          writer->EndObject();
        }

        auto payloadType = static_cast<mozilla::MarkerPayloadType>(
            aEntryReader
                .ReadObject<mozilla::MarkerPayloadTypeUnderlyingType>());

        // Stream the payload, including the type.
        switch (payloadType) {
          case mozilla::MarkerPayloadType::Cpp: {
            mozilla::base_profiler_markers_detail::Streaming::
                MarkerDataDeserializer deserializer =
                    mozilla::base_profiler_markers_detail::Streaming::
                        DeserializerForTag(tag);
            MOZ_RELEASE_ASSERT(deserializer);
            deserializer(aEntryReader, *writer);
            MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
            break;
          }
          case mozilla::MarkerPayloadType::Rust:
            std::forward<RustMarkerCallback>(aRustMarkerCallback)(tag);
            MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
            break;
          default:
            MOZ_ASSERT_UNREACHABLE("Unknown payload type.");
            break;
        }
      }
      writer->EndObject();
    }
  }
  writer->EndArray();
  MOZ_ASSERT(aEntryReader.RemainingBytes() == 0u);
}

}  // namespace mozilla::base_profiler_markers_detail

namespace mozilla {

// ----------------------------------------------------------------------------
// Serializer, Deserializer: ProfilerStringView<CHAR>

// The serialization starts with a ULEB128 number that encodes both whether the
// ProfilerStringView is literal (Least Significant Bit = 0) or not (LSB = 1),
// plus the string length (excluding null terminator) in bytes, shifted left by
// 1 bit. Following that number:
// - If literal, the string pointer value.
// - If non-literal, the contents as bytes (excluding null terminator if any).
template <typename CHAR>
struct ProfileBufferEntryWriter::Serializer<ProfilerStringView<CHAR>> {
  static Length Bytes(const ProfilerStringView<CHAR>& aString) {
    MOZ_RELEASE_ASSERT(
        aString.Length() < std::numeric_limits<Length>::max() / 2,
        "Double the string length doesn't fit in Length type");
    const Length stringLength = static_cast<Length>(aString.Length());
    if (aString.IsLiteral()) {
      // Literal -> Length shifted left and LSB=0, then pointer.
      return ULEB128Size(stringLength << 1 | 0u) +
             static_cast<ProfileChunkedBuffer::Length>(sizeof(const CHAR*));
    }
    // Non-literal -> Length shifted left and LSB=1, then string size in bytes.
    return ULEB128Size((stringLength << 1) | 1u) + stringLength * sizeof(CHAR);
  }

  static void Write(ProfileBufferEntryWriter& aEW,
                    const ProfilerStringView<CHAR>& aString) {
    MOZ_RELEASE_ASSERT(
        aString.Length() < std::numeric_limits<Length>::max() / 2,
        "Double the string length doesn't fit in Length type");
    const Span<const CHAR> span = aString;
    if (aString.IsLiteral()) {
      // Literal -> Length shifted left and LSB=0, then pointer.
      aEW.WriteULEB128(span.Length() << 1 | 0u);
      aEW.WriteObject(WrapProfileBufferRawPointer(span.Elements()));
      return;
    }
    // Non-literal -> Length shifted left and LSB=1, then string size in bytes.
    aEW.WriteULEB128(span.Length() << 1 | 1u);
    aEW.WriteBytes(span.Elements(), span.LengthBytes());
  }
};

template <typename CHAR>
struct ProfileBufferEntryReader::Deserializer<ProfilerStringView<CHAR>> {
  static void ReadInto(ProfileBufferEntryReader& aER,
                       ProfilerStringView<CHAR>& aString) {
    aString = Read(aER);
  }

  static ProfilerStringView<CHAR> Read(ProfileBufferEntryReader& aER) {
    const Length lengthAndIsLiteral = aER.ReadULEB128<Length>();
    const Length stringLength = lengthAndIsLiteral >> 1;
    if ((lengthAndIsLiteral & 1u) == 0u) {
      // LSB==0 -> Literal string, read the string pointer.
      return ProfilerStringView<CHAR>(
          aER.ReadObject<const CHAR*>(), stringLength,
          ProfilerStringView<CHAR>::Ownership::Literal);
    }
    // LSB==1 -> Not a literal string.
    ProfileBufferEntryReader::DoubleSpanOfConstBytes spans =
        aER.ReadSpans(stringLength * sizeof(CHAR));
    if (MOZ_LIKELY(spans.IsSingleSpan()) &&
        reinterpret_cast<uintptr_t>(spans.mFirstOrOnly.Elements()) %
                alignof(CHAR) ==
            0u) {
      // Only a single span, correctly aligned for the CHAR type, we can just
      // refer to it directly, assuming that this ProfilerStringView will not
      // outlive the chunk.
      return ProfilerStringView<CHAR>(
          reinterpret_cast<const CHAR*>(spans.mFirstOrOnly.Elements()),
          stringLength, ProfilerStringView<CHAR>::Ownership::Reference);
    } else {
      // Two spans, we need to concatenate them; or one span, but misaligned.
      // Allocate a buffer to store the string (plus terminal, for safety), and
      // give it to the ProfilerStringView; Note that this is a secret use of
      // ProfilerStringView, which is intended to only be used between
      // deserialization and JSON streaming.
      CHAR* buffer = new CHAR[stringLength + 1];
      spans.CopyBytesTo(buffer);
      buffer[stringLength] = CHAR(0);
      return ProfilerStringView<CHAR>(
          buffer, stringLength,
          ProfilerStringView<CHAR>::Ownership::OwnedThroughStringView);
    }
  }
};

// Serializer, Deserializer: MarkerCategory

// The serialization contains both category numbers encoded as ULEB128.
template <>
struct ProfileBufferEntryWriter::Serializer<MarkerCategory> {
  static Length Bytes(const MarkerCategory& aCategory) {
    return ULEB128Size(static_cast<uint32_t>(aCategory.CategoryPair()));
  }

  static void Write(ProfileBufferEntryWriter& aEW,
                    const MarkerCategory& aCategory) {
    aEW.WriteULEB128(static_cast<uint32_t>(aCategory.CategoryPair()));
  }
};

template <>
struct ProfileBufferEntryReader::Deserializer<MarkerCategory> {
  static void ReadInto(ProfileBufferEntryReader& aER,
                       MarkerCategory& aCategory) {
    aCategory = Read(aER);
  }

  static MarkerCategory Read(ProfileBufferEntryReader& aER) {
    return MarkerCategory(static_cast<baseprofiler::ProfilingCategoryPair>(
        aER.ReadULEB128<uint32_t>()));
  }
};

// ----------------------------------------------------------------------------
// Serializer, Deserializer: MarkerTiming

// The serialization starts with the marker phase, followed by one or two
// timestamps as needed.
template <>
struct ProfileBufferEntryWriter::Serializer<MarkerTiming> {
  static Length Bytes(const MarkerTiming& aTiming) {
    MOZ_ASSERT(!aTiming.IsUnspecified());
    const auto phase = aTiming.MarkerPhase();
    switch (phase) {
      case MarkerTiming::Phase::Instant:
        return SumBytes(phase, aTiming.StartTime());
      case MarkerTiming::Phase::Interval:
        return SumBytes(phase, aTiming.StartTime(), aTiming.EndTime());
      case MarkerTiming::Phase::IntervalStart:
        return SumBytes(phase, aTiming.StartTime());
      case MarkerTiming::Phase::IntervalEnd:
        return SumBytes(phase, aTiming.EndTime());
      default:
        MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
                           phase == MarkerTiming::Phase::Interval ||
                           phase == MarkerTiming::Phase::IntervalStart ||
                           phase == MarkerTiming::Phase::IntervalEnd);
        return 0;  // Only to avoid build errors.
    }
  }

  static void Write(ProfileBufferEntryWriter& aEW,
                    const MarkerTiming& aTiming) {
    MOZ_ASSERT(!aTiming.IsUnspecified());
    const auto phase = aTiming.MarkerPhase();
    switch (phase) {
      case MarkerTiming::Phase::Instant:
        aEW.WriteObjects(phase, aTiming.StartTime());
        return;
      case MarkerTiming::Phase::Interval:
        aEW.WriteObjects(phase, aTiming.StartTime(), aTiming.EndTime());
        return;
      case MarkerTiming::Phase::IntervalStart:
        aEW.WriteObjects(phase, aTiming.StartTime());
        return;
      case MarkerTiming::Phase::IntervalEnd:
        aEW.WriteObjects(phase, aTiming.EndTime());
        return;
      default:
        MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
                           phase == MarkerTiming::Phase::Interval ||
                           phase == MarkerTiming::Phase::IntervalStart ||
                           phase == MarkerTiming::Phase::IntervalEnd);
        return;
    }
  }
};

template <>
struct ProfileBufferEntryReader::Deserializer<MarkerTiming> {
  static void ReadInto(ProfileBufferEntryReader& aER, MarkerTiming& aTiming) {
    aTiming.mPhase = aER.ReadObject<MarkerTiming::Phase>();
    switch (aTiming.mPhase) {
      case MarkerTiming::Phase::Instant:
        aTiming.mStartTime = aER.ReadObject<TimeStamp>();
        aTiming.mEndTime = TimeStamp{};
        break;
      case MarkerTiming::Phase::Interval:
        aTiming.mStartTime = aER.ReadObject<TimeStamp>();
        aTiming.mEndTime = aER.ReadObject<TimeStamp>();
        break;
      case MarkerTiming::Phase::IntervalStart:
        aTiming.mStartTime = aER.ReadObject<TimeStamp>();
        aTiming.mEndTime = TimeStamp{};
        break;
      case MarkerTiming::Phase::IntervalEnd:
        aTiming.mStartTime = TimeStamp{};
        aTiming.mEndTime = aER.ReadObject<TimeStamp>();
        break;
      default:
        MOZ_RELEASE_ASSERT(aTiming.mPhase == MarkerTiming::Phase::Instant ||
                           aTiming.mPhase == MarkerTiming::Phase::Interval ||
                           aTiming.mPhase ==
                               MarkerTiming::Phase::IntervalStart ||
                           aTiming.mPhase == MarkerTiming::Phase::IntervalEnd);
        break;
    }
  }

  static MarkerTiming Read(ProfileBufferEntryReader& aER) {
    TimeStamp start;
    TimeStamp end;
    auto phase = aER.ReadObject<MarkerTiming::Phase>();
    switch (phase) {
      case MarkerTiming::Phase::Instant:
        start = aER.ReadObject<TimeStamp>();
        break;
      case MarkerTiming::Phase::Interval:
        start = aER.ReadObject<TimeStamp>();
        end = aER.ReadObject<TimeStamp>();
        break;
      case MarkerTiming::Phase::IntervalStart:
        start = aER.ReadObject<TimeStamp>();
        break;
      case MarkerTiming::Phase::IntervalEnd:
        end = aER.ReadObject<TimeStamp>();
        break;
      default:
        MOZ_RELEASE_ASSERT(phase == MarkerTiming::Phase::Instant ||
                           phase == MarkerTiming::Phase::Interval ||
                           phase == MarkerTiming::Phase::IntervalStart ||
                           phase == MarkerTiming::Phase::IntervalEnd);
        break;
    }
    return MarkerTiming(start, end, phase);
  }
};

// ----------------------------------------------------------------------------
// Serializer, Deserializer: MarkerStack

// The serialization only contains the `ProfileChunkedBuffer` from the
// backtrace; if there is no backtrace or if it's empty, this will implicitly
// store a nullptr (see
// `ProfileBufferEntryWriter::Serializer<ProfilerChunkedBuffer*>`).
template <>
struct ProfileBufferEntryWriter::Serializer<MarkerStack> {
  static Length Bytes(const MarkerStack& aStack) {
    return SumBytes(aStack.GetChunkedBuffer());
  }

  static void Write(ProfileBufferEntryWriter& aEW, const MarkerStack& aStack) {
    aEW.WriteObject(aStack.GetChunkedBuffer());
  }
};

template <>
struct ProfileBufferEntryReader::Deserializer<MarkerStack> {
  static void ReadInto(ProfileBufferEntryReader& aER, MarkerStack& aStack) {
    aStack = Read(aER);
  }

  static MarkerStack Read(ProfileBufferEntryReader& aER) {
    return MarkerStack(aER.ReadObject<UniquePtr<ProfileChunkedBuffer>>());
  }
};

// ----------------------------------------------------------------------------
// Serializer, Deserializer: MarkerOptions

// The serialization contains all members (either trivially-copyable, or they
// provide their specialization above).
template <>
struct ProfileBufferEntryWriter::Serializer<MarkerOptions> {
  static Length Bytes(const MarkerOptions& aOptions) {
    return SumBytes(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(),
                    aOptions.InnerWindowId());
  }

  static void Write(ProfileBufferEntryWriter& aEW,
                    const MarkerOptions& aOptions) {
    aEW.WriteObjects(aOptions.ThreadId(), aOptions.Timing(), aOptions.Stack(),
                     aOptions.InnerWindowId());
  }
};

template <>
struct ProfileBufferEntryReader::Deserializer<MarkerOptions> {
  static void ReadInto(ProfileBufferEntryReader& aER, MarkerOptions& aOptions) {
    aER.ReadIntoObjects(aOptions.mThreadId, aOptions.mTiming, aOptions.mStack,
                        aOptions.mInnerWindowId);
  }

  static MarkerOptions Read(ProfileBufferEntryReader& aER) {
    MarkerOptions options;
    ReadInto(aER, options);
    return options;
  }
};

}  // namespace mozilla

#endif  // BaseProfilerMarkersDetail_h
