blob: 3342adee2b6e043fd6cd5a8b219cc44ed21a4e03 [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/parser.h"
namespace perfetto {
namespace protovm {
Parser::Parser(protozero::ConstBytes program, Executor* executor)
: program_{program}, executor_(executor) {}
StatusOr<void> Parser::Run(RoCursor src, RwProto::Cursor dst) {
cursors_.src = src;
cursors_.dst = dst;
return ParseInstructions(program_.instructions());
}
StatusOr<void> Parser::ParseInstructions(
protozero::RepeatedFieldIterator<protozero::ConstBytes> it_instruction) {
int instruction_index = 0;
for (; it_instruction; ++it_instruction) {
auto instruction = protos::pbzero::VmInstruction::Decoder(*it_instruction);
auto status = ParseInstruction(instruction);
if (status.IsAbort()) {
PROTOVM_RETURN(status, "instruction[%d]", instruction_index);
}
if (status.IsError()) {
using AbortLevel = perfetto::protos::pbzero::VmInstruction::AbortLevel;
auto abort_level =
instruction.has_abort_level()
? static_cast<AbortLevel>(instruction.abort_level())
: AbortLevel::SKIP_CURRENT_INSTRUCTION_AND_BREAK_OUTER;
if (abort_level == AbortLevel::SKIP_CURRENT_INSTRUCTION_AND_BREAK_OUTER) {
break;
}
if (abort_level == AbortLevel::ABORT) {
PROTOVM_ABORT(
"instruction[%d]: returned status = error and instruction's "
"abort level = 'abort'",
instruction_index);
}
}
++instruction_index;
}
return StatusOr<void>::Ok();
}
StatusOr<void> Parser::ParseInstruction(
const protos::pbzero::VmInstruction::Decoder& instruction) {
if (instruction.has_select()) {
auto status = ParseSelect(instruction);
PROTOVM_RETURN(status, "select");
}
if (instruction.has_reg_load()) {
auto status = ParseRegLoad(instruction);
PROTOVM_RETURN_IF_NOT_OK(status, "reg_load");
} else if (instruction.has_del()) {
auto status = executor_->Delete(&cursors_.dst);
PROTOVM_RETURN_IF_NOT_OK(status, "del");
} else if (instruction.has_merge()) {
auto status = executor_->Merge(&cursors_);
PROTOVM_RETURN_IF_NOT_OK(status, "merge");
} else if (instruction.has_set()) {
auto status = executor_->Set(&cursors_);
PROTOVM_RETURN_IF_NOT_OK(status, "set");
} else {
PROTOVM_ABORT("Unsupported instruction");
}
return ParseInstructions(instruction.nested_instructions());
}
StatusOr<void> Parser::ParseRegLoad(
const protos::pbzero::VmInstruction::Decoder& instruction) {
auto saved_selected = cursors_.selected;
protos::pbzero::VmOpRegLoad::Decoder reg_load(instruction.reg_load());
cursors_.selected = !reg_load.has_cursor()
? CursorEnum::VM_CURSOR_SRC
: static_cast<CursorEnum>(reg_load.cursor());
auto status = executor_->WriteRegister(
cursors_, static_cast<uint8_t>(reg_load.dst_register()));
cursors_.selected = saved_selected;
PROTOVM_RETURN_IF_NOT_OK(status);
return status;
}
StatusOr<void> Parser::ParseSelect(
const protos::pbzero::VmInstruction::Decoder& instruction) {
auto saved_cursors = cursors_;
protos::pbzero::VmOpSelect::Decoder select(instruction.select());
cursors_.selected = !select.has_cursor()
? CursorEnum::VM_CURSOR_SRC
: static_cast<CursorEnum>(select.cursor());
cursors_.create_if_not_exist = select.create_if_not_exist();
if (cursors_.selected == CursorEnum::VM_CURSOR_SRC &&
cursors_.create_if_not_exist) {
PROTOVM_ABORT(
"incompatible params: src cursor (read only) + create_if_not_exist");
}
auto status = ParseSelectRec(instruction, select.relative_path());
cursors_ = saved_cursors;
return status;
}
StatusOr<void> Parser::ParseSelectRec(
const protos::pbzero::VmInstruction::Decoder& instruction,
protozero::RepeatedFieldIterator<protozero::ConstBytes> it_path_component) {
protos::pbzero::VmOpSelect::Decoder select(instruction.select());
bool has_entered_all_path_components = !it_path_component;
if (has_entered_all_path_components) {
return ParseInstructions(instruction.nested_instructions());
}
auto curr_component =
protos::pbzero::VmOpSelect::PathComponent::Decoder(*it_path_component);
++it_path_component;
std::optional<protos::pbzero::VmOpSelect::PathComponent::Decoder>
next_component =
it_path_component
? std::make_optional(
protos::pbzero::VmOpSelect::PathComponent::Decoder(
*it_path_component))
: std::nullopt;
if (!curr_component.has_field_id()) {
PROTOVM_ABORT("Invalid path. Expected path component with field_id.");
}
// iterate repeated field
if (curr_component.is_repeated()) {
if (cursors_.selected == CursorEnum::VM_CURSOR_SRC) {
auto status_or_it = executor_->IterateRepeatedField(
&cursors_.src, curr_component.field_id());
PROTOVM_RETURN_IF_NOT_OK(status_or_it, "iterate repeated field (id = %d)",
static_cast<int>(curr_component.field_id()));
int index = 0;
for (auto it = *status_or_it; it; ++it) {
cursors_.src = *it;
auto status = ParseSelectRec(instruction, it_path_component);
PROTOVM_RETURN_IF_NOT_OK(status, "repeated field (id = %d, index = %d)",
static_cast<int>(curr_component.field_id()),
index);
++index;
}
} else if (cursors_.selected == CursorEnum::VM_CURSOR_DST) {
auto status_or_it = executor_->IterateRepeatedField(
&cursors_.dst, curr_component.field_id());
PROTOVM_RETURN_IF_NOT_OK(status_or_it, "iterate repeated field (id = %d)",
static_cast<int>(curr_component.field_id()));
int index = 0;
for (auto it = *status_or_it; it; ++it) {
cursors_.dst = it.GetCursor();
auto status = ParseSelectRec(instruction, it_path_component);
PROTOVM_RETURN_IF_NOT_OK(status, "repeated field (id = %d, index = %d)",
static_cast<int>(curr_component.field_id()),
index);
++index;
}
} else {
PROTOVM_ABORT(
"Iteration over selected cursor (%d) is not supported. Should be "
"either SRC or DST cursor.",
static_cast<int>(cursors_.selected));
}
return StatusOr<void>::Ok();
}
// enter indexed repeated field
if (next_component && next_component->has_array_index()) {
++it_path_component;
auto status_enter = executor_->EnterRepeatedFieldAt(
&cursors_, curr_component.field_id(), next_component->array_index());
PROTOVM_RETURN_IF_NOT_OK(status_enter, "enter indexed repeated field");
auto status_select = ParseSelectRec(instruction, it_path_component);
PROTOVM_RETURN(status_select, "repeated field (id = %d, index = %d)",
static_cast<int>(curr_component.field_id()),
static_cast<int>(next_component->array_index()));
}
// enter mapped repeated field
if (next_component && next_component->has_map_key_field_id()) {
++it_path_component;
if (!next_component->has_register_to_match()) {
PROTOVM_ABORT(
"enter mapped repeated field: expected field 'register_to_match'");
}
auto reg_id = static_cast<uint8_t>(next_component->register_to_match());
auto key = executor_->ReadRegister(reg_id);
PROTOVM_RETURN_IF_NOT_OK(key, "enter mapped repeated field");
auto status_enter = executor_->EnterRepeatedFieldByKey(
&cursors_, curr_component.field_id(),
next_component->map_key_field_id(), static_cast<uint32_t>(*key));
PROTOVM_RETURN_IF_NOT_OK(status_enter, "enter mapped repeated field");
auto status_select = ParseSelectRec(instruction, it_path_component);
PROTOVM_RETURN(status_select, "mapped repeated field (id = %d, key = %d)",
static_cast<int>(curr_component.field_id()),
static_cast<int>(*key));
}
// enter field
auto status = executor_->EnterField(&cursors_, curr_component.field_id());
PROTOVM_RETURN_IF_NOT_OK(status, "enter field (id = %d)",
static_cast<int>(curr_component.field_id()));
return ParseSelectRec(instruction, it_path_component);
}
} // namespace protovm
} // namespace perfetto