Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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
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
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