| /* | |
| * Copyright (C) 2026 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/compiler/compiler.h" | |
| #include "perfetto/ext/base/status_macros.h" | |
| #include "perfetto/protozero/scattered_heap_buffer.h" | |
| #include "src/protovm/compiler/compile_config.descriptor.h" | |
| #include "src/protozero/text_to_proto/text_to_proto.h" | |
| namespace perfetto::protovm { | |
| Compiler::Compiler() : emitter_(&pool_) {} | |
| base::StatusOr<std::string> Compiler::Compile( | |
| std::string_view config_textproto, | |
| std::string_view descriptor_bytes) { | |
| RETURN_IF_ERROR(pool_.AddFromFileDescriptorSet( | |
| reinterpret_cast<const uint8_t*>(descriptor_bytes.data()), | |
| descriptor_bytes.size())); | |
| ASSIGN_OR_RETURN( | |
| auto proto, | |
| protozero::TextToProto(perfetto::kCompileConfigDescriptor.data(), | |
| perfetto::kCompileConfigDescriptor.size(), | |
| ".perfetto.protos.CompileConfig", | |
| "compile_config.textproto", config_textproto)); | |
| protos::pbzero::CompileConfig::Decoder config(proto.data(), proto.size()); | |
| if (!config.has_root_message()) { | |
| return base::ErrStatus("Config doesn't specify a root message"); | |
| } | |
| std::string root_message = config.root_message().ToStdString(); | |
| if (!root_message.empty() && root_message[0] != '.') { | |
| root_message = "." + root_message; | |
| } | |
| auto idx = pool_.FindDescriptorIdx(root_message); | |
| if (!idx) { | |
| return base::ErrStatus("Root message '%s' not found in loaded descriptors", | |
| root_message.c_str()); | |
| } | |
| auto* root_proto = &pool_.descriptors()[*idx]; | |
| protozero::HeapBuffered<perfetto::protos::pbzero::VmProgram> program; | |
| auto root = InstructionEmitter::Scope{program.get(), root_proto, root_proto}; | |
| RETURN_IF_ERROR(ParseCommands(root, config.commands())); | |
| return program.SerializeAsString(); | |
| } | |
| base::Status Compiler::ParseCommands( | |
| const InstructionEmitter::Scope& scope, | |
| protozero::RepeatedFieldIterator<protozero::ConstBytes> commands) const { | |
| for (auto it = commands; it; ++it) { | |
| protos::pbzero::CompileCommand::Decoder command(*it); | |
| if (command.has_set()) { | |
| RETURN_IF_ERROR(ParseSet(scope, command)); | |
| } else if (command.has_del()) { | |
| RETURN_IF_ERROR(ParseDel(scope, command)); | |
| } else if (command.has_merge()) { | |
| RETURN_IF_ERROR(ParseMerge(scope, command)); | |
| } else if (command.has_enter_scope()) { | |
| RETURN_IF_ERROR(ParseEnterScope(scope, command)); | |
| } else { | |
| return base::ErrStatus("Unknown command type"); | |
| } | |
| } | |
| return base::OkStatus(); | |
| } | |
| base::Status Compiler::ParseSet( | |
| const InstructionEmitter::Scope& scope, | |
| const protos::pbzero::CompileCommand::Decoder& command) const { | |
| protos::pbzero::CompileCommandSet::Decoder set(command.set()); | |
| auto src_path = ParsePath(set.src()); | |
| auto dst_path = ParsePath(set.dst()); | |
| return emitter_.Set(scope, src_path, dst_path, GetAbortLevel(command)); | |
| } | |
| base::Status Compiler::ParseDel( | |
| const InstructionEmitter::Scope& scope, | |
| const protos::pbzero::CompileCommand::Decoder& command) const { | |
| protos::pbzero::CompileCommandDel::Decoder del(command.del()); | |
| auto src_path = ParsePath(del.src()); | |
| auto dst_path = ParsePath(del.dst()); | |
| if (!(del.if_src_present() ^ del.has_dst_key_field())) { | |
| return base::ErrStatus( | |
| "del command must specify exactly one of if_src_present or " | |
| "dst_key_field"); | |
| } | |
| if (del.if_src_present()) { | |
| return emitter_.DeleteIfPresent(scope, src_path, dst_path, | |
| GetAbortLevel(command)); | |
| } | |
| if (del.has_dst_key_field()) { | |
| return emitter_.DeleteByKey(scope, src_path, dst_path, | |
| del.dst_key_field().ToStdStringView(), | |
| GetAbortLevel(command)); | |
| } | |
| return base::ErrStatus("Unsupported Del command variant"); | |
| } | |
| base::Status Compiler::ParseMerge( | |
| const InstructionEmitter::Scope& scope, | |
| const protos::pbzero::CompileCommand::Decoder& command) const { | |
| protos::pbzero::CompileCommandMerge::Decoder merge(command.merge()); | |
| auto src_path = ParsePath(merge.src()); | |
| auto dst_path = ParsePath(merge.dst()); | |
| auto abort_level = GetAbortLevel(command); | |
| if (merge.has_key_field()) { | |
| return emitter_.MergeByKey(scope, src_path, dst_path, | |
| merge.key_field().ToStdStringView(), | |
| merge.recursive(), abort_level); | |
| } | |
| return emitter_.Merge(scope, src_path, dst_path, merge.recursive(), | |
| abort_level); | |
| } | |
| base::Status Compiler::ParseEnterScope( | |
| const InstructionEmitter::Scope& scope, | |
| const protos::pbzero::CompileCommand::Decoder& command) const { | |
| protos::pbzero::CompileCommandEnterScope::Decoder enter_scope( | |
| command.enter_scope()); | |
| auto src_path = ParsePath(enter_scope.src()); | |
| auto dst_path = ParsePath(enter_scope.dst()); | |
| auto abort_level = GetAbortLevel(command); | |
| ASSIGN_OR_RETURN(auto src_scope, | |
| emitter_.Select(scope, src_path, Cursor::VM_CURSOR_SRC, | |
| false, abort_level)); | |
| ASSIGN_OR_RETURN( | |
| auto dst_scope, | |
| emitter_.Select(src_scope, dst_path, Cursor::VM_CURSOR_DST, true, | |
| InstructionEmitter::kDefaultAbortLevel)); | |
| return ParseCommands(dst_scope, enter_scope.commands()); | |
| } | |
| Compiler::AbortLevel Compiler::GetAbortLevel( | |
| const protos::pbzero::CompileCommand::Decoder& command) const { | |
| if (!command.has_abort_level()) { | |
| return AbortLevel::SKIP_CURRENT_INSTRUCTION_AND_BREAK_OUTER; | |
| } | |
| return static_cast<AbortLevel>(command.abort_level()); | |
| } | |
| } // namespace perfetto::protovm |