blob: ac005ec3bd75026208c48ad6a52f009521aff91b [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_TRACING_CORE_PROTO_ZERO_MESSAGE_H_
#define COMPONENTS_TRACING_CORE_PROTO_ZERO_MESSAGE_H_
#include <stdint.h>
#include <type_traits>
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/template_util.h"
#include "build/build_config.h"
#include "components/tracing/core/proto_utils.h"
#include "components/tracing/core/scattered_stream_writer.h"
#include "components/tracing/tracing_export.h"
namespace tracing {
namespace v2 {
class ProtoZeroMessageHandleBase;
// Base class extended by the proto C++ stubs generated by the ProtoZero
// compiler (see //components/tracing/tools/). This class provides the minimal
// runtime required to support append-only operations and is desiged for
// performance. None of the methods require any dynamic memory allocation.
class TRACING_EXPORT ProtoZeroMessage {
public:
ProtoZeroMessage();
// Clears up the state, allowing the message to be reused as a fresh one.
void Reset(ScatteredStreamWriter*);
// Commits all the changes to the buffer (backfills the size field of this and
// all nested messages) and seals the message. Returns the size of the message
// (and all nested sub-messages), without taking into account any chunking.
// Finalize is idempotent and can be called several times w/o side effects.
size_t Finalize();
// Optional. If is_valid() == true, the corresponding memory region (its
// length == proto::kMessageLengthFieldSize) is backfilled with the size of
// this message (minus |size_already_written| below) when the message is
// finalized. This is the mechanism used by messages to backfill their
// corresponding size field in the parent message.
ContiguousMemoryRange size_field() const { return size_field_; }
void set_size_field(const ContiguousMemoryRange& reserved_range) {
size_field_ = reserved_range;
}
// This is to deal with case of backfilling the size of a root (non-nested)
// message which is split into multiple chunks. Upon finalization only the
// partial size that lies in the last chunk has to be backfilled.
void inc_size_already_written(size_t size) { size_already_written_ += size; }
#if DCHECK_IS_ON()
// Only for ProtoZeroMessageHandleBase.
void set_handle(ProtoZeroMessageHandleBase* handle) { handle_ = handle; }
#endif
protected:
// Proto types: uint64, uint32, int64, int32, bool, enum.
template <typename T>
void AppendVarInt(uint32_t field_id, T value) {
if (nested_message_)
EndNestedMessage();
uint8_t buffer[proto::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
pos = proto::WriteVarInt(proto::MakeTagVarInt(field_id), pos);
// WriteVarInt encodes signed values in two's complement form.
pos = proto::WriteVarInt(value, pos);
WriteToStream(buffer, pos);
}
// Proto types: sint64, sint32.
template <typename T>
void AppendSignedVarInt(uint32_t field_id, T value) {
AppendVarInt(field_id, proto::ZigZagEncode(value));
}
// Proto types: bool, enum (small).
// Faster version of AppendVarInt for tiny numbers.
void AppendTinyVarInt(uint32_t field_id, int32_t value) {
DCHECK(0 <= value && value < 0x80);
if (nested_message_)
EndNestedMessage();
uint8_t buffer[proto::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
// MakeTagVarInt gets super optimized here for constexpr.
pos = proto::WriteVarInt(proto::MakeTagVarInt(field_id), pos);
*pos++ = static_cast<uint8_t>(value);
WriteToStream(buffer, pos);
}
// Proto types: fixed64, sfixed64, fixed32, sfixed32, double, float.
template <typename T>
void AppendFixed(uint32_t field_id, T value) {
if (nested_message_)
EndNestedMessage();
uint8_t buffer[proto::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
pos = proto::WriteVarInt(proto::MakeTagFixed<T>(field_id), pos);
memcpy(pos, &value, sizeof(T));
pos += sizeof(T);
// TODO(kraynov): Optimize memcpy performance, see http://crbug.com/624311 .
WriteToStream(buffer, pos);
}
void AppendString(uint32_t field_id, const char* str);
void AppendBytes(uint32_t field_id, const void* value, size_t size);
// Begins a nested message, using the static storage provided by the parent
// class (see comment in |nested_messages_arena_|). The nested message ends
// either when Finalize() is called or when any other Append* method is called
// in the parent class.
// The template argument T is supposed to be a stub class auto generated from
// a .proto, hence a subclass of ProtoZeroMessage.
template <class T>
T* BeginNestedMessage(uint32_t field_id) {
// This is to prevent subclasses (which should be autogenerated, though), to
// introduce extra state fields (which wouldn't be initialized by Reset()).
static_assert(std::is_base_of<ProtoZeroMessage, T>::value,
"T must be a subclass of ProtoZeroMessage");
static_assert(sizeof(T) == sizeof(ProtoZeroMessage),
"ProtoZeroMessage subclasses cannot introduce extra state.");
T* message = reinterpret_cast<T*>(nested_messages_arena_);
BeginNestedMessageInternal(field_id, message);
return message;
}
private:
friend class ProtoZeroMessageTest;
FRIEND_TEST_ALL_PREFIXES(ProtoZeroMessageTest, BasicTypesNoNesting);
FRIEND_TEST_ALL_PREFIXES(ProtoZeroMessageTest, BackfillSizeOnFinalization);
FRIEND_TEST_ALL_PREFIXES(ProtoZeroMessageTest, NestedMessagesSimple);
FRIEND_TEST_ALL_PREFIXES(ProtoZeroMessageTest, StressTest);
FRIEND_TEST_ALL_PREFIXES(ProtoZeroMessageTest, MessageHandle);
enum : uint32_t { kMaxNestingDepth = 8 };
void BeginNestedMessageInternal(uint32_t field_id, ProtoZeroMessage*);
// Called by Finalize and Append* methods.
void EndNestedMessage();
void WriteToStream(const uint8_t* src_begin, const uint8_t* src_end) {
#if DCHECK_IS_ON()
DCHECK(!sealed_);
#endif
DCHECK(src_begin < src_end);
const size_t size = static_cast<size_t>(src_end - src_begin);
stream_writer_->WriteBytes(src_begin, size);
size_ += size;
}
// Only POD fields are allowed. This class's dtor is never called.
// See the comment on the static_assert in the the corresponding .cc file.
// The stream writer interface used for the serialization.
ScatteredStreamWriter* stream_writer_;
// Keeps track of the size of the current message.
size_t size_;
ContiguousMemoryRange size_field_;
size_t size_already_written_;
// Used to detect attemps to create messages with a nesting level >
// kMaxNestingDepth. |nesting_depth_| == 0 for root (non-nested) messages.
uint32_t nesting_depth_;
#if DCHECK_IS_ON()
// When true, no more changes to the message are allowed. This is to DCHECK
// attempts of writing to a message which has been Finalize()-d.
bool sealed_;
ProtoZeroMessageHandleBase* handle_;
#endif
// Pointer to the last child message created through BeginNestedMessage(), if
// any. nullptr otherwise. There is no need to keep track of more than one
// message per nesting level as the proto-zero API contract mandates that
// nested fields can be filled only in a stacked fashion. In other words,
// nested messages are finalized and sealed when any other field is set in the
// parent message (or the parent message itself is finalized) and cannot be
// accessed anymore afterwards.
ProtoZeroMessage* nested_message_;
// The root message owns the storage for all its nested messages, up to a max
// of kMaxNestingDepth levels (see the .cc file). Note that the boundaries of
// the arena are meaningful only for the root message. The static_assert in
// the .cc file guarantees that the sizeof(nested_messages_arena_) is enough
// to contain up to kMaxNestingDepth messages.
alignas(sizeof(void*)) uint8_t nested_messages_arena_[512];
// DO NOT add any fields below |nested_messages_arena_|. The memory layout of
// nested messages would overflow the storage allocated by the root message.
DISALLOW_COPY_AND_ASSIGN(ProtoZeroMessage);
};
} // namespace v2
} // namespace tracing
#endif // COMPONENTS_TRACING_CORE_PROTO_ZERO_MESSAGE_H_