blob: 48c9d6c673a3a3a7c3a4ea564f2d5243773ec320 [file] [log] [blame]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/protovm/rw_proto_cursor.h"
#include "perfetto/protozero/proto_decoder.h"
namespace perfetto {
namespace protovm {
RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator()
: allocator_{nullptr}, it_{} {}
RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator(
Allocator* allocator,
IntrusiveMap::Iterator it)
: allocator_{allocator}, it_{it} {}
RwProtoCursor::RepeatedFieldIterator&
RwProtoCursor::RepeatedFieldIterator::RepeatedFieldIterator::operator++() {
++it_;
return *this;
}
RwProtoCursor RwProtoCursor::RepeatedFieldIterator::GetCursor() {
return RwProtoCursor{it_->value.get(), allocator_};
}
RwProtoCursor::RepeatedFieldIterator::operator bool() const {
return static_cast<bool>(it_);
}
RwProtoCursor::RwProtoCursor() = default;
RwProtoCursor::RwProtoCursor(Node* node, Allocator* allocator)
: node_{node}, allocator_{allocator} {}
StatusOr<bool> RwProtoCursor::HasField(uint32_t field_id) {
PERFETTO_DCHECK(node_);
// Eagerly decompose bytes because the field being tested will be entered
// later anyways. See Executor::EnterField().
auto status = ConvertToMessageIfNeeded(node_);
PROTOVM_RETURN_IF_NOT_OK(status);
auto* message = node_->GetIf<Node::Message>();
bool found = static_cast<bool>(message->field_id_to_node.Find(field_id));
return found;
}
StatusOr<void> RwProtoCursor::EnterField(uint32_t field_id) {
PERFETTO_DCHECK(node_);
auto status_or_it = FindOrCreateMessageField(node_, field_id);
PROTOVM_RETURN_IF_NOT_OK(status_or_it);
auto it = *status_or_it;
if (it->value->GetIf<Node::IndexedRepeatedField>()) {
PROTOVM_ABORT(
"Attempted to enter field (id=%u) as a simple field but it is an "
"indexed repeated field",
field_id);
}
if (it->value->GetIf<Node::MappedRepeatedField>()) {
PROTOVM_ABORT(
"Attempted to enter field (id=%u) as a simple field but it is a "
"mapped repeated field",
field_id);
}
holding_map_and_node_ = {&node_->GetIf<Node::Message>()->field_id_to_node,
std::addressof(*it)};
node_ = it->value.get();
return StatusOr<void>::Ok();
}
StatusOr<void> RwProtoCursor::EnterRepeatedFieldAt(uint32_t field_id,
uint32_t index) {
PERFETTO_DCHECK(node_);
auto status_or_message_field = FindOrCreateMessageField(node_, field_id);
PROTOVM_RETURN_IF_NOT_OK(status_or_message_field);
auto message_field = *status_or_message_field;
auto status =
ConvertToIndexedRepeatedFieldIfNeeded(message_field->value.get());
PROTOVM_RETURN_IF_NOT_OK(status);
auto status_or_repeated_field =
FindOrCreateIndexedRepeatedField(message_field->value.get(), index);
PROTOVM_RETURN_IF_NOT_OK(status_or_repeated_field);
holding_map_and_node_ = {
&message_field->value->GetIf<Node::IndexedRepeatedField>()->index_to_node,
std::addressof(**status_or_repeated_field)};
node_ = (*status_or_repeated_field)->value.get();
return StatusOr<void>::Ok();
}
StatusOr<RwProtoCursor::RepeatedFieldIterator>
RwProtoCursor::IterateRepeatedField(uint32_t field_id) {
PERFETTO_DCHECK(node_);
auto status_convertion_to_message = ConvertToMessageIfNeeded(node_);
PROTOVM_RETURN_IF_NOT_OK(status_convertion_to_message);
auto* message = node_->GetIf<Node::Message>();
auto it = message->field_id_to_node.Find(field_id);
if (!it) {
return RepeatedFieldIterator{};
}
auto* field = it->value.get();
auto status_convertion_to_repeated_field =
ConvertToIndexedRepeatedFieldIfNeeded(field);
PROTOVM_RETURN_IF_NOT_OK(status_convertion_to_repeated_field);
return RepeatedFieldIterator{
allocator_,
field->GetIf<Node::IndexedRepeatedField>()->index_to_node.begin()};
}
StatusOr<void> RwProtoCursor::EnterRepeatedFieldByKey(uint32_t field_id,
uint32_t map_key_field_id,
uint64_t key) {
PERFETTO_DCHECK(node_);
auto status_or_message_field = FindOrCreateMessageField(node_, field_id);
PROTOVM_RETURN_IF_NOT_OK(status_or_message_field);
auto message_field = *status_or_message_field;
auto status_conversion = ConvertToMappedRepeatedFieldIfNeeded(
message_field->value.get(), map_key_field_id);
PROTOVM_RETURN_IF_NOT_OK(status_conversion);
auto status_or_repeated_field =
FindOrCreateMappedRepeatedField(message_field->value.get(), key);
PROTOVM_RETURN_IF_NOT_OK(status_or_repeated_field);
holding_map_and_node_ = {
&message_field->value->GetIf<Node::MappedRepeatedField>()->key_to_node,
std::addressof(**status_or_repeated_field)};
node_ = (*status_or_repeated_field)->value.get();
return StatusOr<void>::Ok();
}
StatusOr<Scalar> RwProtoCursor::GetScalar() const {
PERFETTO_DCHECK(node_);
auto* scalar = node_->GetIf<Scalar>();
if (!scalar) {
PROTOVM_ABORT("Attempted \"get scalar\" operation but node has type %s",
node_->GetTypeName());
}
return *scalar;
}
StatusOr<void> RwProtoCursor::SetBytes(protozero::ConstBytes data) {
PERFETTO_DCHECK(node_);
if (bool is_compatible = node_->GetIf<Node::Empty>() ||
node_->GetIf<Node::Bytes>() ||
node_->GetIf<Node::Message>();
!is_compatible) {
PROTOVM_ABORT("Attempted \"set bytes\" operation but node has type %s",
node_->GetTypeName());
}
auto status_or_bytes = allocator_->AllocateAndCopyBytes(data);
PROTOVM_RETURN_IF_NOT_OK(status_or_bytes);
allocator_->DeleteReferencedData(node_);
node_->value = Node::Bytes{std::move(*status_or_bytes)};
return StatusOr<void>::Ok();
}
StatusOr<void> RwProtoCursor::SetScalar(Scalar scalar) {
PERFETTO_DCHECK(node_);
if (bool is_compatible =
node_->GetIf<Node::Empty>() || node_->GetIf<Scalar>();
!is_compatible) {
PROTOVM_ABORT("Attempted \"set scalar\" operation but node has type %s",
node_->GetTypeName());
}
node_->value = scalar;
return StatusOr<void>::Ok();
}
StatusOr<void> RwProtoCursor::Merge(protozero::ConstBytes data) {
PERFETTO_DCHECK(node_);
if (bool is_compatible = node_->GetIf<Node::Empty>() ||
node_->GetIf<Node::Message>() ||
node_->GetIf<Node::Bytes>();
!is_compatible) {
PROTOVM_ABORT("Attempted MERGE operation but node has type %s",
node_->GetTypeName());
}
if (data.size == 0) {
return StatusOr<void>::Ok();
}
auto status_convertion = ConvertToMessageIfNeeded(node_);
PROTOVM_RETURN_IF_NOT_OK(status_convertion);
auto* message = node_->GetIf<Node::Message>();
protozero::ProtoDecoder decoder(data);
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
auto status_or_map_value = CreateNodeFromField(field);
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
auto it = message->field_id_to_node.Find(field.id());
if (!it) {
auto status_or_it = MapInsert(&message->field_id_to_node, field.id(),
std::move(*status_or_map_value));
PROTOVM_RETURN_IF_NOT_OK(status_or_it);
continue;
}
if (it->value->GetIf<Node::MappedRepeatedField>()) {
PROTOVM_ABORT(
"Merge operation of mapped repeated field is not supported (field id "
"= %u)",
field.id());
}
if (auto* indexed_fields = it->value->GetIf<Node::IndexedRepeatedField>()) {
// Implements merge semantics for repeated fields: all existing fields are
// removed and replaced with the newly received fields.
if (!indexed_fields->has_been_merged) {
// Optimization opportunity: reuse the existing nodes to avoid N
// allocation-deallocation pairs, where N is the number of newly
// received repeated fields.
allocator_->DeleteReferencedData(it->value.get());
indexed_fields->has_been_merged = true;
}
MapInsert(&indexed_fields->index_to_node,
indexed_fields->index_to_node.Size(),
std::move(*status_or_map_value));
continue;
}
// Optimization oppurtunity: reuse the existing node to avoid one
// allocation-deallocation pair
allocator_->Delete(it->value.release());
it->value = std::move(*status_or_map_value);
}
// Reset the merge state of repeated fields
for (auto& field : message->field_id_to_node) {
if (auto* indexed_fields =
field.value->GetIf<Node::IndexedRepeatedField>()) {
indexed_fields->has_been_merged = false;
}
}
return StatusOr<void>::Ok();
}
StatusOr<void> RwProtoCursor::Delete() {
PERFETTO_DCHECK(node_);
bool is_root_node = !holding_map_and_node_.first;
if (is_root_node) {
node_->value = Node::Empty{};
return StatusOr<void>::Ok();
}
auto [holding_map, map_node] = holding_map_and_node_;
PERFETTO_DCHECK(holding_map);
PERFETTO_DCHECK(map_node);
holding_map->Remove(*map_node);
allocator_->Delete(&GetOuterNode(*map_node));
node_ = nullptr; // Delete operation invalidates cursor
return StatusOr<void>::Ok();
}
StatusOr<void> RwProtoCursor::ConvertToMessageIfNeeded(Node* node) {
if (node->GetIf<Node::Message>()) {
return StatusOr<void>::Ok();
}
if (node->GetIf<Node::Empty>()) {
node->value = Node::Message{};
return StatusOr<void>::Ok();
}
auto* bytes = node->GetIf<Node::Bytes>();
if (!bytes) {
PROTOVM_ABORT("Attempted conversion to message but node has type %s",
node->GetTypeName());
}
Node::Message message;
protozero::ProtoDecoder decoder(protozero::ConstBytes{
static_cast<const uint8_t*>(bytes->data.get()), bytes->size});
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
auto status_or_map_value = CreateNodeFromField(field);
if (!status_or_map_value.IsOk()) {
allocator_->DeleteReferencedData(&message);
PROTOVM_RETURN(status_or_map_value);
}
auto it = message.field_id_to_node.Find(field.id());
// First occurrence of this field id. Just insert a new field into the
// map.
if (!it) {
auto status_or_it = MapInsert(&message.field_id_to_node, field.id(),
std::move(*status_or_map_value));
if (!status_or_it.IsOk()) {
allocator_->DeleteReferencedData(&message);
PROTOVM_RETURN(status_or_it, "Insert message field (id = %u)",
field.id());
}
continue;
}
// Nth occurrence of this field id:
// 1. Make sure we have an IndexedRepeatedField node
// 2. Append into the IndexedRepeatedField's map
auto status_conversion =
ConvertToIndexedRepeatedFieldIfNeeded(it->value.get());
if (!status_conversion.IsOk()) {
allocator_->DeleteReferencedData(&message);
allocator_->Delete(status_or_map_value->release());
PROTOVM_RETURN(status_conversion);
}
auto& index_to_node =
it->value->GetIf<Node::IndexedRepeatedField>()->index_to_node;
auto status_or_it = MapInsert(&index_to_node, index_to_node.Size(),
std::move(*status_or_map_value));
if (!status_or_it.IsOk()) {
allocator_->DeleteReferencedData(&message);
PROTOVM_RETURN(status_or_it,
"Insert repeated field (id = %u, index = %d)", field.id(),
static_cast<int>(index_to_node.Size()));
}
}
allocator_->DeleteReferencedData(node);
node->value = message;
return StatusOr<void>::Ok();
}
StatusOr<OwnedPtr<Node>> RwProtoCursor::CreateNodeFromField(
protozero::Field field) {
if (field.type() == protozero::proto_utils::ProtoWireType::kLengthDelimited) {
auto status_or_bytes = allocator_->AllocateAndCopyBytes(field.as_bytes());
PROTOVM_RETURN_IF_NOT_OK(status_or_bytes);
auto status_or_node =
allocator_->CreateNode<Node::Bytes>(std::move(*status_or_bytes));
if (!status_or_node.IsOk()) {
allocator_->DeleteReferencedData(&status_or_bytes.value());
PROTOVM_RETURN(status_or_node);
}
return std::move(*status_or_node);
}
auto status_or_node =
allocator_->CreateNode<Scalar>(field.type(), field.as_uint64());
if (!status_or_node.IsOk()) {
PROTOVM_RETURN(status_or_node);
}
return std::move(*status_or_node);
}
StatusOr<void> RwProtoCursor::ConvertToMappedRepeatedFieldIfNeeded(
Node* node,
uint32_t map_key_field_id) {
if (node->GetIf<Node::MappedRepeatedField>()) {
return StatusOr<void>::Ok();
}
if (node->GetIf<Node::Empty>()) {
node->value = Node::MappedRepeatedField{};
return StatusOr<void>::Ok();
}
// If the current node contains a message (either raw bytes or a decomposed
// Node::Message), convert it into a Node::MappedRepeatedField.
//
// The process is:
// 1. Extract the key (identified by 'map_key_field_id') from the message data
// within the current 'node'.
// 2. Allocate a new node. The original message content from the current node
// is moved into this new node.
// 3. Repurpose the current node, replacing it with an empty
// Node::MappedRepeatedField
// (intrusive tree that stores message fields ordered by key).
// 4. Insert the new node (containing Node::Message) into the repurposed
// current node (Node::MappedRepeatedField) using the key extracted in step 1.
if (node->GetIf<Node::Bytes>() || node->GetIf<Node::Message>()) {
auto status_or_key = ReadScalarField(*node, map_key_field_id);
PROTOVM_RETURN_IF_NOT_OK(status_or_key);
auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
auto map_value = std::move(*status_or_map_value);
map_value->value = std::move(node->value);
node->value = Node::MappedRepeatedField{};
auto status_or_it =
MapInsert(&node->GetIf<Node::MappedRepeatedField>()->key_to_node,
*status_or_key, std::move(map_value));
PROTOVM_RETURN(status_or_it);
}
// If the current node is a Node::IndexedRepeatedField (intrusive tree with
// message fields ordered by index), this block converts it into a
// Node::MappedRepeatedField (intrusive tree with message fields ordered by
// key).
//
// The process is:
// 1. Initialize an empty Node::MappedRepeatedField
// 2. For each field in the current node (Node::IndexedRepeatedField):
// 2.1. Extract the key (identified by 'map_key_field_id'). Note that the
// iterated fields must be messages.
// 2.2. Remove the field from the current node
// (Node::IndexedRepeatedField).
// 2.3. Insert the field into the Node::MappedRepeatedField using the key
// extracted in step 2.1.
// 3. Assign the Node::MappedRepeatedField to the current node, i.e. repurpose
// current node from Node::IndexedRepeatedField to
// Node::MappedRepeatedField)
if (auto* indexed = node->GetIf<Node::IndexedRepeatedField>()) {
Node::MappedRepeatedField mapped_repeated_field;
for (auto it = indexed->index_to_node.begin(); it;) {
auto& map_entry = *it;
auto& value_node = *it->value;
it = indexed->index_to_node.Remove(it);
auto status_or_key = ReadScalarField(value_node, map_key_field_id);
PROTOVM_RETURN_IF_NOT_OK(status_or_key);
map_entry.key = *status_or_key;
mapped_repeated_field.key_to_node.Insert(map_entry);
}
node->value = mapped_repeated_field;
return StatusOr<void>::Ok();
}
PROTOVM_ABORT(
"Attempted to access field as MappedRepeatedField but node has type "
"%s",
node->GetTypeName());
}
StatusOr<void> RwProtoCursor::ConvertToIndexedRepeatedFieldIfNeeded(
Node* node) {
if (node->GetIf<Node::IndexedRepeatedField>()) {
return StatusOr<void>::Ok();
}
if (node->GetIf<Node::MappedRepeatedField>()) {
PROTOVM_ABORT(
"Attempted \"convert to indexed repeated field\" operation but "
"node has type %s",
node->GetTypeName());
}
if (node->GetIf<Node::Empty>()) {
node->value = Node::IndexedRepeatedField{};
return StatusOr<void>::Ok();
}
auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
auto map_value = std::move(*status_or_map_value);
map_value->value = std::move(node->value);
node->value = Node::IndexedRepeatedField{};
auto status_or_it =
MapInsert(&node->GetIf<Node::IndexedRepeatedField>()->index_to_node, 0,
std::move(map_value));
PROTOVM_RETURN(status_or_it);
}
StatusOr<IntrusiveMap::Iterator> RwProtoCursor::FindOrCreateMessageField(
Node* node,
uint32_t field_id) {
auto status = ConvertToMessageIfNeeded(node);
PROTOVM_RETURN_IF_NOT_OK(status);
auto* message = node->GetIf<Node::Message>();
auto it = message->field_id_to_node.Find(field_id);
if (it) {
return it;
}
auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
auto status_or_it = MapInsert(&message->field_id_to_node, field_id,
std::move(*status_or_map_value));
PROTOVM_RETURN(status_or_it);
}
StatusOr<IntrusiveMap::Iterator>
RwProtoCursor::FindOrCreateIndexedRepeatedField(Node* node, uint32_t index) {
auto& index_to_node =
node->GetIf<Node::IndexedRepeatedField>()->index_to_node;
auto it = index_to_node.Find(index);
if (it) {
return it;
}
bool requires_creation_and_is_not_simple_append =
index > index_to_node.Size();
if (requires_creation_and_is_not_simple_append) {
PROTOVM_ABORT(
"Attempted to insert repeated field at arbitrary position (only "
"append operation is supported)");
}
auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
return MapInsert(&node->GetIf<Node::IndexedRepeatedField>()->index_to_node,
index, std::move(*status_or_map_value));
}
StatusOr<IntrusiveMap::Iterator> RwProtoCursor::FindOrCreateMappedRepeatedField(
Node* node,
uint64_t key) {
auto it = node->GetIf<Node::MappedRepeatedField>()->key_to_node.Find(key);
if (it) {
return it;
}
auto status_or_map_value = allocator_->CreateNode<Node::Empty>();
PROTOVM_RETURN_IF_NOT_OK(status_or_map_value);
return MapInsert(&node->GetIf<Node::MappedRepeatedField>()->key_to_node, key,
std::move(*status_or_map_value));
}
StatusOr<IntrusiveMap::Iterator> RwProtoCursor::MapInsert(
IntrusiveMap* map,
uint64_t key,
OwnedPtr<Node> map_value) {
auto status_or_map_node =
allocator_->CreateNode<Node::MapNode>(key, std::move(map_value));
if (!status_or_map_node.IsOk()) {
allocator_->Delete(map_value.release());
PROTOVM_RETURN(status_or_map_node, "Failed to allocate node");
}
auto [it, inserted] =
map->Insert(*status_or_map_node->release()->GetIf<Node::MapNode>());
if (!inserted) {
allocator_->Delete(map_value.release());
allocator_->Delete(status_or_map_node->release());
PROTOVM_ABORT(
"Failed to insert intrusive map entry (key = %d). Duplicated key?",
static_cast<int>(key));
}
return it;
}
StatusOr<uint64_t> RwProtoCursor::ReadScalarField(const Node& node,
uint32_t field_id) {
if (auto* bytes = node.GetIf<Node::Bytes>()) {
protozero::ProtoDecoder decoder(protozero::ConstBytes{
static_cast<const uint8_t*>(bytes->data.get()), bytes->size});
auto field = decoder.ReadField();
while (field.valid() && field.id() != field_id) {
field = decoder.ReadField();
}
if (!field.valid()) {
PROTOVM_ABORT(
"Attempted to read scalar field (id=%u) but it is not present",
field_id);
}
if (field.type() ==
protozero::proto_utils::ProtoWireType::kLengthDelimited) {
PROTOVM_ABORT("Attempted to length-delimited field (id=%u) as scalar",
field_id);
}
return field.as_uint64();
}
if (auto* message = node.GetIf<Node::Message>()) {
auto it = message->field_id_to_node.Find(field_id);
if (!it) {
PROTOVM_ABORT(
"Attempted to read scalar field (id=%u) but it is not present",
field_id);
}
auto* scalar = it->value->GetIf<Scalar>();
if (!scalar) {
PROTOVM_ABORT(
"Attempted to read scalar field (id=%u) from node with type %s",
field_id, it->value->GetTypeName());
}
return scalar->value;
}
PROTOVM_ABORT(
"Attempted to read scalar field (id=%u) but parent node has type %s",
field_id, node.GetTypeName());
}
} // namespace protovm
} // namespace perfetto