| // Copyright 2023 Google LLC |
| // |
| // 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 |
| // |
| // https://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 "mpact/sim/decoder/instruction_set_visitor.h" |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <cstddef> |
| #include <filesystem> // NOLINT: third party. |
| #include <fstream> |
| #include <iostream> |
| #include <istream> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/flags/flag.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "antlr4-runtime/ParserRuleContext.h" |
| #include "mpact/sim/decoder/bundle.h" |
| #include "mpact/sim/decoder/decoder_error_listener.h" |
| #include "mpact/sim/decoder/format_name.h" |
| #include "mpact/sim/decoder/instruction_set.h" |
| #include "mpact/sim/decoder/instruction_set_contexts.h" |
| #include "mpact/sim/decoder/opcode.h" |
| #include "mpact/sim/decoder/slot.h" |
| #include "mpact/sim/decoder/template_expression.h" |
| #include "re2/re2.h" |
| |
| // This flag is used to set the version of the generated code. Version 1 is |
| // the default version. Version 2 adds an instruction pointer to the resource |
| // and operator functions in the EncodingInterface. |
| ABSL_FLAG(unsigned, generator, 1, "Version of generated code"); |
| |
| namespace mpact { |
| namespace sim { |
| namespace machine_description { |
| namespace instruction_set { |
| |
| static absl::StatusOr<TemplateValue> AbsoluteValueTemplateFunc( |
| TemplateInstantiationArgs* args) { |
| if (args->size() != 1) { |
| return absl::InternalError(absl::StrCat( |
| "Wrong number of arguments, expected 1, was given ", args->size())); |
| } |
| auto result = (*args)[0]->GetValue(); |
| if (!result.ok()) return result.status(); |
| |
| auto* value_ptr = std::get_if<int>(&result.value()); |
| if (value_ptr == nullptr) { |
| return absl::InternalError("Type mismatch - int expected"); |
| } |
| int return_value = (*value_ptr < 0) ? -(*value_ptr) : *value_ptr; |
| return TemplateValue(return_value); |
| } |
| |
| InstructionSetVisitor::InstructionSetVisitor() { |
| template_function_evaluators_.insert(std::make_pair( |
| "abs", TemplateFunctionEvaluator(AbsoluteValueTemplateFunc, 1))); |
| } |
| |
| InstructionSetVisitor::~InstructionSetVisitor() { |
| for (auto& [unused, expr_ptr] : constant_map_) { |
| delete expr_ptr; |
| } |
| constant_map_.clear(); |
| for (auto* wrapper : antlr_parser_wrappers_) { |
| delete wrapper; |
| } |
| antlr_parser_wrappers_.clear(); |
| for (auto* expr : disasm_field_widths_) delete expr; |
| disasm_field_widths_.clear(); |
| } |
| |
| // Main entry point for processing the file. |
| absl::Status InstructionSetVisitor::Process( |
| const std::vector<std::string>& file_names, const std::string& prefix, |
| const std::string& isa_name, const std::vector<std::string>& include_roots, |
| absl::string_view directory) { |
| generator_version_ = absl::GetFlag(FLAGS_generator); |
| // Create and add the error listener. |
| set_error_listener(std::make_unique<decoder::DecoderErrorListener>()); |
| if (isa_name.empty()) { |
| error_listener()->semanticError(nullptr, "Isa name cannot be empty"); |
| return absl::InvalidArgumentError("Isa name cannot be empty"); |
| } |
| |
| for (auto& include_root : include_roots) { |
| include_dir_vec_.push_back(include_root); |
| } |
| |
| // Add the directory of the input file to the include roots. |
| if (!file_names.empty()) { |
| std::string dir = |
| std::filesystem::path(file_names[0]).parent_path().string(); |
| auto it = std::find(include_dir_vec_.begin(), include_dir_vec_.end(), dir); |
| if (it == include_dir_vec_.end()) { |
| include_dir_vec_.push_back(dir); |
| } |
| } |
| |
| std::string isa_prefix = prefix; |
| std::istream* source_stream = &std::cin; |
| |
| if (!file_names.empty()) { |
| source_stream = new std::fstream(file_names[0], std::fstream::in); |
| } |
| // Create an antlr4 stream from the input stream. |
| IsaAntlrParserWrapper parser_wrapper(source_stream); |
| error_listener()->set_file_name(file_names[0]); |
| file_names_.push_back(file_names[0]); |
| parser_wrapper.parser()->removeErrorListeners(); |
| parser_wrapper.parser()->addErrorListener(error_listener()); |
| |
| // Parse the file and then create the data structures. |
| TopLevelCtx* top_level = parser_wrapper.parser()->top_level(); |
| |
| if (!file_names.empty()) { |
| delete source_stream; |
| source_stream = nullptr; |
| } |
| |
| if (error_listener()->HasError() > 0) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| |
| // Visit the parse tree starting at the namespaces declaration. |
| VisitTopLevel(top_level); |
| // Process additional source files. |
| for (int i = 1; i < file_names.size(); ++i) { |
| // Add the directory of the input file to the include roots if not already |
| // present. |
| std::string dir = |
| std::filesystem::path(file_names[i]).parent_path().string(); |
| auto it = std::find(include_dir_vec_.begin(), include_dir_vec_.end(), dir); |
| if (it == include_dir_vec_.end()) { |
| include_dir_vec_.push_back(dir); |
| } |
| ParseIncludeFile(top_level, file_names[i], {}); |
| } |
| // Now process the parse tree. |
| auto instruction_set = ProcessTopLevel(isa_name); |
| // Include files may generate additional syntax errors. |
| if (instruction_set == nullptr) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| // Verify that all referenced bundles and slots were defined. |
| PerformBundleReferenceChecks(instruction_set.get(), |
| instruction_set->bundle()); |
| if (error_listener()->HasError() > 0) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| // Analyze resource use and partition resources into simple and complex |
| // resources. |
| auto status = instruction_set->AnalyzeResourceUse(); |
| if (!status.ok()) { |
| error_listener()->semanticError(nullptr, status.message()); |
| } |
| |
| // If the prefix is empty, use the source file name. |
| if (isa_prefix.empty() && file_names.empty()) { |
| error_listener()->semanticError(nullptr, |
| "No prefix or file name specified"); |
| } else if (isa_prefix.empty()) { |
| isa_prefix = |
| ToSnakeCase(std::filesystem::path(file_names[0]).stem().string()); |
| } |
| // Check for additional errors. |
| if (error_listener()->HasError() > 0) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| std::string encoding_type_name = |
| absl::StrCat(ToPascalCase(isa_name), "EncodingBase"); |
| |
| // Create output streams for .h and .cc files. |
| std::string dec_dot_h_name = absl::StrCat(isa_prefix, "_decoder.h"); |
| std::string dec_dot_cc_name = absl::StrCat(isa_prefix, "_decoder.cc"); |
| std::string enc_dot_h_name = absl::StrCat(isa_prefix, "_encoder.h"); |
| std::string enc_dot_cc_name = absl::StrCat(isa_prefix, "_encoder.cc"); |
| std::string enum_h_name = absl::StrCat(isa_prefix, "_enums.h"); |
| std::string enum_cc_name = absl::StrCat(isa_prefix, "_enums.cc"); |
| std::ofstream dec_dot_h_file(absl::StrCat(directory, "/", dec_dot_h_name)); |
| std::ofstream dec_dot_cc_file(absl::StrCat(directory, "/", dec_dot_cc_name)); |
| std::ofstream enc_dot_h_file(absl::StrCat(directory, "/", enc_dot_h_name)); |
| std::ofstream enc_dot_cc_file(absl::StrCat(directory, "/", enc_dot_cc_name)); |
| std::ofstream enum_h_file(absl::StrCat(directory, "/", enum_h_name)); |
| std::ofstream enum_cc_file(absl::StrCat(directory, "/", enum_cc_name)); |
| // Generate the code, close the files and return. |
| std::string guard_name = ToHeaderGuard(dec_dot_h_name); |
| // Decoder .h file. |
| dec_dot_h_file << GenerateHdrFileProlog(dec_dot_h_name, enum_h_name, |
| guard_name, encoding_type_name, |
| instruction_set->namespaces()); |
| dec_dot_h_file << instruction_set->GenerateClassDeclarations( |
| dec_dot_h_name, enum_h_name, encoding_type_name); |
| dec_dot_h_file << GenerateHdrFileEpilog(guard_name, |
| instruction_set->namespaces()); |
| dec_dot_h_file.close(); |
| // Decoder .cc file. |
| dec_dot_cc_file << GenerateCcFileProlog(dec_dot_h_name, /*use_includes=*/true, |
| instruction_set->namespaces()); |
| dec_dot_cc_file << instruction_set->GenerateClassDefinitions( |
| dec_dot_h_name, encoding_type_name); |
| dec_dot_cc_file << GenerateNamespaceEpilog(instruction_set->namespaces()); |
| dec_dot_cc_file.close(); |
| |
| // Enum files. |
| enum_h_file << GenerateSimpleHdrProlog(ToHeaderGuard(enum_h_name), |
| instruction_set->namespaces()); |
| enum_cc_file << GenerateCcFileProlog(enum_h_name, /*use_includes=*/false, |
| instruction_set->namespaces()); |
| auto [h_output, cc_output] = instruction_set->GenerateEnums(enum_h_name); |
| enum_h_file << h_output; |
| enum_cc_file << cc_output; |
| enum_h_file << GenerateHdrFileEpilog(ToHeaderGuard(enum_h_name), |
| instruction_set->namespaces()); |
| enum_cc_file << GenerateNamespaceEpilog(instruction_set->namespaces()); |
| enum_h_file.close(); |
| enum_cc_file.close(); |
| |
| // Encoder files |
| guard_name = ToHeaderGuard(enc_dot_h_name); |
| auto [enc_dot_h_prolog, enc_dot_cc_prolog] = |
| GenerateEncFilePrologs(enc_dot_h_name, guard_name, enum_h_name, |
| encoding_type_name, instruction_set->namespaces()); |
| enc_dot_h_file << enc_dot_h_prolog; |
| enc_dot_cc_file << enc_dot_cc_prolog; |
| auto [h_enc, cc_enc] = instruction_set->GenerateEncClasses( |
| enc_dot_h_name, enum_h_name, encoding_type_name); |
| enc_dot_h_file << h_enc; |
| enc_dot_cc_file << cc_enc; |
| enc_dot_h_file << GenerateHdrFileEpilog(guard_name, |
| instruction_set->namespaces()); |
| enc_dot_cc_file << GenerateNamespaceEpilog( |
| instruction_set->namespaces()); // Enum .h and .cc files. |
| enc_dot_h_file.close(); |
| enc_dot_cc_file.close(); |
| |
| return absl::OkStatus(); |
| } |
| |
| void InstructionSetVisitor::PerformBundleReferenceChecks( |
| InstructionSet* instruction_set, Bundle* bundle) { |
| // Verify that all referenced bundles were declared. |
| for (const auto& bundle_name : bundle->bundle_names()) { |
| Bundle* bundle_ref = instruction_set->GetBundle(bundle_name); |
| // Perform the check recursively on the referenced bundles. |
| PerformBundleReferenceChecks(instruction_set, bundle_ref); |
| } |
| // Verify that all the slot uses were declared. |
| for (auto& [slot_name, instance_vec] : bundle->slot_uses()) { |
| Slot* slot = instruction_set->GetSlot(slot_name); |
| // Verify that the instance number of the slot falls within valid range. |
| for (auto& instance_number : instance_vec) { |
| if (instance_number >= slot->size()) { |
| auto* token = bundle->ctx() == nullptr ? nullptr : bundle->ctx()->start; |
| error_listener()->semanticError( |
| token, |
| absl::StrCat("Index ", instance_number, " out of range for slot ", |
| slot_name, "' referenced in bundle '", bundle->name(), |
| "'")); |
| continue; |
| } |
| } |
| if (slot->is_referenced()) continue; |
| if ((slot->default_instruction() == nullptr) || |
| slot->default_instruction()->semfunc_code_string().empty()) { |
| error_listener()->semanticError( |
| slot->ctx()->start, |
| absl::StrCat("Slot '", slot->name(), |
| "' lacks a default semantic action")); |
| } |
| slot->set_is_referenced(true); |
| } |
| instruction_set->ComputeSlotAndBundleOrders(); |
| } |
| |
| void InstructionSetVisitor::VisitTopLevel(TopLevelCtx* ctx) { |
| auto declarations = ctx->declaration(); |
| |
| // Process disasm widths. Only the one in the top level file is used if there |
| // are additional ones in included files. |
| int count = 0; |
| DisasmWidthsCtx* disasm_ctx = nullptr; |
| for (auto* decl : declarations) { |
| context_file_map_[decl] = current_file_index_; |
| if (decl->disasm_widths() == nullptr) continue; |
| if (count > 0) { |
| error_listener()->semanticError( |
| decl->disasm_widths()->start, |
| absl::StrCat("Only one `disasm width` declaration allowed - " |
| "previous declaration on line: ", |
| disasm_ctx->start->getLine())); |
| } |
| disasm_ctx = decl->disasm_widths(); |
| context_file_map_[disasm_ctx] = context_file_map_.at(decl); |
| VisitDisasmWidthsDecl(decl->disasm_widths()); |
| count++; |
| } |
| |
| // Parse, but don't process all the slots, bundles, isas and include files. |
| PreProcessDeclarations(declarations); |
| } |
| |
| std::unique_ptr<InstructionSet> InstructionSetVisitor::ProcessTopLevel( |
| absl::string_view isa_name) { |
| // At this point we have the contexts for all isas, bundles, and slots. |
| // First make sure that the named isa has been defined. |
| auto isa_ptr = isa_decl_map_.find(isa_name); |
| if (isa_ptr == isa_decl_map_.end()) { |
| error_listener()->semanticError( |
| nullptr, absl::StrCat("No isa '", isa_name, "' declared")); |
| return nullptr; |
| } |
| // Visit the Isa. |
| auto isa = VisitIsaDeclaration(isa_ptr->second); |
| return isa; |
| } |
| |
| void InstructionSetVisitor::PreProcessDeclarations( |
| const std::vector<DeclarationCtx*>& ctx_vec) { |
| std::vector<IncludeFileCtx*> include_files; |
| // Get handles to the slot, bundle and isa declarations. |
| |
| // Create map from slot name to slot ctx. |
| for (auto* decl : ctx_vec) { |
| if (decl->slot_declaration() != nullptr) { |
| auto* slot_ctx = decl->slot_declaration(); |
| context_file_map_.insert({slot_ctx, current_file_index_}); |
| auto name = slot_ctx->slot_name->getText(); |
| auto ptr = slot_decl_map_.find(name); |
| if (ptr != slot_decl_map_.end()) { |
| error_listener()->semanticError( |
| slot_ctx->start, |
| absl::StrCat("Slot '", name, |
| "' already declared - previous declaration on line: ", |
| slot_ctx->start->getLine())); |
| } |
| slot_decl_map_.emplace(name, slot_ctx); |
| } |
| // Create map from bundle name to bundle ctx. |
| if (decl->bundle_declaration() != nullptr) { |
| auto* bundle_ctx = decl->bundle_declaration(); |
| context_file_map_.insert({bundle_ctx, current_file_index_}); |
| auto name = bundle_ctx->bundle_name->getText(); |
| auto ptr = bundle_decl_map_.find(name); |
| if (ptr != bundle_decl_map_.end()) { |
| error_listener()->semanticError( |
| bundle_ctx->start, |
| absl::StrCat("Bundle '", name, |
| "' already declared - previous declaration on line: ", |
| bundle_ctx->start->getLine())); |
| continue; |
| } |
| bundle_decl_map_.emplace(name, bundle_ctx); |
| } |
| // Create map from isa name to isa ctx. |
| if (decl->isa_declaration() != nullptr) { |
| auto* isa_ctx = decl->isa_declaration(); |
| context_file_map_.insert({isa_ctx, current_file_index_}); |
| auto name = isa_ctx->instruction_set_name->getText(); |
| auto ptr = isa_decl_map_.find(name); |
| if (ptr != isa_decl_map_.end()) { |
| error_listener()->semanticError( |
| isa_ctx->start, |
| absl::StrCat("Isa '", name, |
| "' already declared - previous declaration on line: ", |
| isa_ctx->start->getLine())); |
| continue; |
| } |
| isa_decl_map_.emplace(name, isa_ctx); |
| } |
| |
| // Process global include file specifications. |
| if (decl->include_file_list() != nullptr) { |
| for (auto* include_file : decl->include_file_list()->include_file()) { |
| // Insert the string - the call will always succeed, but the insertion |
| // does not happen if it already exists. |
| include_files_.insert(include_file->STRING_LITERAL()->getText()); |
| } |
| } |
| |
| // Process global constants. |
| if (decl->constant_def() != nullptr) { |
| context_file_map_.insert({decl->constant_def(), current_file_index_}); |
| VisitConstantDef(decl->constant_def()); |
| } |
| |
| // Process .isa include file. |
| if (decl->include_file() != nullptr) { |
| include_files.push_back(decl->include_file()); |
| } |
| } |
| // Process all include files - this adds to all isa, slot and bundle |
| // context maps, as well as all global variables, etc. |
| for (auto* include_file_ctx : include_files) { |
| VisitIncludeFile(include_file_ctx); |
| } |
| } |
| |
| std::unique_ptr<InstructionSet> InstructionSetVisitor::VisitIsaDeclaration( |
| IsaDeclCtx* ctx) { |
| if (ctx == nullptr) return nullptr; |
| auto instruction_set = |
| std::make_unique<InstructionSet>(ctx->instruction_set_name->getText()); |
| // An InstructionSet also acts as (has-a bundle) - it's the top level |
| // bundle. |
| instruction_set->set_bundle( |
| new Bundle(instruction_set->name(), instruction_set.get(), nullptr)); |
| // Visit the namespace declaration, and the bundle and slot references that |
| // are part of the instruction_set declaration. |
| VisitNamespaceDecl(ctx->namespace_decl(), instruction_set.get()); |
| context_file_map_[ctx->bundle_list()] = context_file_map_.at(ctx); |
| VisitBundleList(ctx->bundle_list(), instruction_set->bundle()); |
| context_file_map_[ctx->slot_list()] = context_file_map_.at(ctx); |
| VisitSlotList(ctx->slot_list(), instruction_set->bundle()); |
| return instruction_set; |
| } |
| |
| void InstructionSetVisitor::VisitNamespaceDecl(NamespaceDeclCtx* ctx, |
| InstructionSet* isa) { |
| if (ctx == nullptr) return; |
| for (auto* namespace_name : ctx->namespace_ident()) { |
| isa->namespaces().push_back(namespace_name->getText()); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitBundleList(BundleListCtx* ctx, |
| Bundle* bundle) { |
| if (ctx == nullptr) return; |
| // Append the list of named bundles referenced within the containing bundle. |
| auto bundle_specs_vec = ctx->bundle_spec(); |
| for (auto bundle_spec : bundle_specs_vec) { |
| auto bundle_name = bundle_spec->IDENT()->getText(); |
| auto iter = bundle_decl_map_.find(bundle_name); |
| // If the name hasn't been seen, flag an error. |
| if (iter == bundle_decl_map_.end()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], bundle_spec->start, |
| absl::StrCat("Reference to undefined bundle: '", bundle_name, "'")); |
| continue; |
| } |
| // If the bundle hasn't been processed yet, visit its declaration. |
| if (!bundle->instruction_set()->bundle_map().contains(bundle_name)) { |
| VisitBundleDeclaration(iter->second, bundle->instruction_set()); |
| } |
| bundle->AppendBundleName(bundle_spec->IDENT()->getText()); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitSlotList(SlotListCtx* ctx, Bundle* bundle) { |
| if (ctx == nullptr) return; |
| // Append the list of named slots referenced within the containing bundle. |
| auto slot_specs_vec = ctx->slot_spec(); |
| for (auto slot_spec : slot_specs_vec) { |
| auto slot_name = slot_spec->IDENT()->getText(); |
| auto iter = slot_decl_map_.find(slot_name); |
| // If the slot hasn't been seen, flag an error. |
| if (iter == slot_decl_map_.end()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], slot_spec->start, |
| absl::StrCat("Reference to undefined slot: '", slot_name, "'")); |
| continue; |
| } |
| // If the slot hasn't been processed yet, visit its declaration. |
| if (!bundle->instruction_set()->slot_map().contains(slot_name)) { |
| VisitSlotDeclaration(iter->second, bundle->instruction_set()); |
| } |
| // First obtain the vector of instance indices specified before appending. |
| std::vector<int> instances = VisitArraySpec(slot_spec->array_spec()); |
| bundle->AppendSlot(slot_name, instances); |
| } |
| } |
| |
| std::vector<int> InstructionSetVisitor::VisitArraySpec(ArraySpecCtx* ctx) { |
| std::vector<int> instances; |
| |
| // If there are not array specifications, return the empty vector. |
| if (ctx == nullptr) return instances; |
| |
| auto range_spec_vec = ctx->range_spec(); |
| for (auto range_spec : range_spec_vec) { |
| // The range spec is on the form of n, or m..n. Add the appropriate |
| // indices to the instances vector. |
| int range_start; |
| int range_end; |
| range_end = range_start = std::stoi(range_spec->range_start->getText()); |
| if (range_spec->range_end != nullptr) { |
| range_end = std::stoi(range_spec->range_end->getText()); |
| } |
| for (int instance = range_start; instance <= range_end; instance++) { |
| instances.push_back(instance); |
| } |
| } |
| return instances; |
| } |
| |
| void InstructionSetVisitor::VisitConstantDef(ConstantDefCtx* ctx) { |
| if (ctx == nullptr) return; |
| std::string ident = ctx->ident()->getText(); |
| std::string type = ctx->template_parameter_type()->getText(); |
| context_file_map_.insert({ctx->expression(), context_file_map_.at(ctx)}); |
| auto* expr = VisitExpression(ctx->expression(), nullptr, nullptr); |
| auto status = AddConstant(ident, type, expr); |
| if (!status.ok()) { |
| delete expr; |
| error_listener()->semanticError(ctx->ident()->start, status.message()); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitIncludeFile(IncludeFileCtx* ctx) { |
| // The literal includes the double quotes. |
| std::string literal = ctx->STRING_LITERAL()->getText(); |
| // Remove the double quotes from the literal and construct the full file |
| // name. |
| std::string file_name = literal.substr(1, literal.length() - 2); |
| // Check for recursive include. |
| for (auto const& name : include_file_stack_) { |
| if (name == file_name) { |
| error_listener()->semanticError( |
| ctx->start, |
| absl::StrCat(": ", "Recursive include of '", file_name, "'")); |
| return; |
| } |
| } |
| ParseIncludeFile(ctx, file_name, include_dir_vec_); |
| } |
| |
| void InstructionSetVisitor::ParseIncludeFile( |
| antlr4::ParserRuleContext* ctx, const std::string& file_name, |
| const std::vector<std::string>& dirs) { |
| std::fstream include_file; |
| // Open include file. |
| include_file.open(file_name, std::fstream::in); |
| std::string include_name; |
| if (!include_file.is_open()) { |
| // Try each of the include file directories. |
| for (auto const& dir : dirs) { |
| include_name = dir + "/" + file_name; |
| include_file.open(include_name, std::fstream::in); |
| if (include_file.is_open()) break; |
| } |
| if (!include_file.is_open()) { |
| error_listener()->semanticError( |
| ctx == nullptr ? nullptr : ctx->start, |
| absl::StrCat("Failed to open '", file_name, "'", " ", dirs.size())); |
| return; |
| } |
| } |
| // Add the directory of the include file to the include roots if not already |
| // present. |
| std::string dir = std::filesystem::path(include_name).parent_path().string(); |
| auto it = std::find(include_dir_vec_.begin(), include_dir_vec_.end(), dir); |
| if (it == include_dir_vec_.end()) { |
| include_dir_vec_.push_back(dir); |
| } |
| std::string previous_file_name = error_listener()->file_name(); |
| int previous_file_index_ = current_file_index_; |
| error_listener()->set_file_name(file_name); |
| file_names_.push_back(file_name); |
| current_file_index_ = file_names_.size() - 1; |
| // Create an antlr4 stream from the input stream. |
| auto* include_parser = new IsaAntlrParserWrapper(&include_file); |
| // We need to save the parser state so it's available for analysis after |
| // we are done with building the parse trees. |
| antlr_parser_wrappers_.push_back(include_parser); |
| // Add the error listener. |
| include_parser->parser()->removeErrorListeners(); |
| include_parser->parser()->addErrorListener(error_listener()); |
| // Start parsing at the include_top_level rule. |
| auto declaration_vec = |
| include_parser->parser()->include_top_level()->declaration(); |
| include_file.close(); |
| if (error_listener()->syntax_error_count() > 0) { |
| error_listener()->set_file_name(previous_file_name); |
| current_file_index_ = previous_file_index_; |
| return; |
| } |
| include_file_stack_.push_back(file_name); |
| PreProcessDeclarations(declaration_vec); |
| include_file_stack_.pop_back(); |
| error_listener()->set_file_name(previous_file_name); |
| current_file_index_ = previous_file_index_; |
| } |
| |
| void InstructionSetVisitor::VisitBundleDeclaration( |
| BundleDeclCtx* ctx, InstructionSet* instruction_set) { |
| if (ctx == nullptr) return; |
| Bundle* bundle = |
| new Bundle(ctx->bundle_name->getText(), instruction_set, ctx); |
| instruction_set->AddBundle(bundle); |
| int num_slot_lists = 0; |
| int num_bundle_lists = 0; |
| int num_include_file_lists = 0; |
| int num_semfunc_specs = 0; |
| for (auto* part : ctx->bundle_parts()) { |
| if (part->slot_list() != nullptr) { |
| if (num_slot_lists > 0) { |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| part->start, |
| "Multiple slot lists in bundle"); |
| return; |
| } |
| context_file_map_[part->slot_list()] = context_file_map_.at(ctx); |
| VisitSlotList(part->slot_list(), bundle); |
| num_slot_lists++; |
| continue; |
| } |
| if (part->bundle_list() != nullptr) { |
| if (num_bundle_lists > 0) { |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| part->start, |
| "Multiple bundle lists in bundle"); |
| return; |
| } |
| context_file_map_[part->bundle_list()] = context_file_map_.at(ctx); |
| VisitBundleList(part->bundle_list(), bundle); |
| num_bundle_lists++; |
| continue; |
| } |
| if (part->include_file_list() != nullptr) { |
| if (num_include_file_lists > 0) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], part->start, |
| "Multiple include file lists in bundle"); |
| return; |
| } |
| for (auto* include_file : part->include_file_list()->include_file()) { |
| // Insert the string - the call will always succeed, but the insertion |
| // does not happen if it already exists. |
| include_files_.insert(include_file->STRING_LITERAL()->getText()); |
| } |
| num_include_file_lists++; |
| continue; |
| } |
| if (part->semfunc_spec() != nullptr) { |
| if (num_semfunc_specs > 0) { |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| part->start, |
| "Multiple semfunc specs in bundle"); |
| return; |
| } |
| std::string string_literal = |
| part->semfunc_spec()->STRING_LITERAL(0)->getText(); |
| // Strip double quotes. |
| std::string code_string = |
| string_literal.substr(1, string_literal.length() - 2); |
| bundle->set_semfunc_code_string(code_string); |
| num_semfunc_specs++; |
| continue; |
| } |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| part->start, "Unhandled bundle part type"); |
| return; |
| } |
| } |
| |
| void InstructionSetVisitor::VisitSlotDeclaration( |
| SlotDeclCtx* ctx, InstructionSet* instruction_set) { |
| bool is_templated = ctx->template_decl() != nullptr; |
| Slot* slot = new Slot(ctx->slot_name->getText(), instruction_set, |
| is_templated, ctx, generator_version_); |
| if (is_templated) { |
| for (auto const& param : ctx->template_decl()->template_parameter_decl()) { |
| auto status = slot->AddTemplateFormal(param->IDENT()->getText()); |
| if (!status.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], param->start, |
| status.message()); |
| } |
| } |
| } |
| // Set the base slot if it inherits. |
| if (ctx->base_item_list() != nullptr) { |
| // For each entry in the list of slots to derive from. |
| for (auto* base_item : ctx->base_item_list()->base_item()) { |
| std::string base_name = base_item->IDENT()->getText(); |
| // If the base slot does has not been seen - undefined error. |
| auto slot_iter = slot_decl_map_.find(base_name); |
| if (slot_iter == slot_decl_map_.end()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], base_item->start, |
| absl::StrCat("Undefined base slot: ", base_name)); |
| continue; |
| } |
| // If the slot hasn't been visited, visit it. |
| auto* base = instruction_set->GetSlot(base_name); |
| if (base == nullptr) { |
| VisitSlotDeclaration(slot_iter->second, instruction_set); |
| base = instruction_set->GetSlot(base_name); |
| } |
| // Now check if the base slot is templated or not, and if the template |
| // arguments are present or not. |
| auto* template_spec = base_item->template_spec(); |
| if ((template_spec != nullptr) && !base->is_templated()) { |
| // Template arguments are present but the slot isn't templated. |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], base_item->start, |
| absl::StrCat("'", base_name, "' is not a templated slot")); |
| continue; |
| } |
| if ((template_spec == nullptr) && base->is_templated()) { |
| // The slot is templated, but no template arguments. |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], base_item->start, |
| absl::StrCat("Missing template arguments for slot '", base_name, |
| "'")); |
| continue; |
| } |
| if (template_spec != nullptr) { |
| // Check that the number of arguments match. |
| int arg_count = template_spec->expression().size(); |
| int param_count = base->template_parameters().size(); |
| if (arg_count != param_count) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], template_spec->start, |
| absl::StrCat("Wrong number of arguments: ", param_count, |
| " were expected, ", arg_count, " were provided")); |
| continue; |
| } |
| bool has_error = false; |
| // Build up the argument vector. |
| auto* arguments = new TemplateInstantiationArgs; |
| for (auto* template_arg : template_spec->expression()) { |
| context_file_map_.insert({template_arg, context_file_map_.at(ctx)}); |
| auto* expr = VisitExpression(template_arg, slot, nullptr); |
| if (expr == nullptr) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], template_arg->start, |
| "Error in template expression"); |
| has_error = true; |
| break; |
| } |
| arguments->push_back(expr); |
| } |
| if (has_error) { |
| for (auto* expr : *arguments) { |
| delete expr; |
| } |
| delete arguments; |
| continue; |
| } |
| auto result = slot->AddBase(base, arguments); |
| if (!result.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], base_item->start, |
| result.message()); |
| } |
| } else { |
| // No template arguments. |
| auto result = slot->AddBase(base); |
| if (!result.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], base_item->start, |
| result.message()); |
| } |
| } |
| } |
| } |
| // Set the size if it is replicated. |
| if (ctx->size_spec() != nullptr) { |
| int size = std::stoi(ctx->size_spec()->NUMBER()->getText(), nullptr, 0); |
| slot->set_size(size); |
| } |
| // Add the slot to the ISA. |
| instruction_set->AddSlot(slot); |
| for (auto* decl_ctx : ctx->const_and_default_decl()) { |
| context_file_map_[decl_ctx] = context_file_map_.at(ctx); |
| VisitConstAndDefaultDecls(decl_ctx, slot); |
| } |
| context_file_map_[ctx->opcode_list()] = context_file_map_.at(ctx); |
| VisitOpcodeList(ctx->opcode_list(), slot); |
| } |
| |
| void InstructionSetVisitor::VisitDisasmWidthsDecl(DisasmWidthsCtx* ctx) { |
| for (auto* expr : ctx->expression()) { |
| context_file_map_.insert({expr, context_file_map_.at(ctx)}); |
| auto* width_expr = VisitExpression(expr, nullptr, nullptr); |
| if ((width_expr == nullptr) || (!width_expr->IsConstant())) { |
| error_listener()->semanticError(expr->start, |
| "Expression must be constant"); |
| continue; |
| } |
| disasm_field_widths_.push_back(width_expr); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitConstAndDefaultDecls(ConstAndDefaultCtx* ctx, |
| Slot* slot) { |
| if (ctx == nullptr) return; |
| // A constant declaration. |
| auto* const_def = ctx->constant_def(); |
| if (const_def != nullptr) { // Constant declaration. |
| std::string ident = const_def->ident()->getText(); |
| std::string type = const_def->template_parameter_type()->getText(); |
| context_file_map_.insert( |
| {const_def->expression(), context_file_map_.at(ctx)}); |
| auto* expr = VisitExpression(const_def->expression(), slot, nullptr); |
| if (expr == nullptr) { |
| delete expr; |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| const_def->expression()->start, |
| "Error in expression"); |
| return; |
| } |
| auto status = slot->AddConstant(ident, type, expr); |
| if (!status.ok()) { |
| delete expr; |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| const_def->ident()->start, |
| status.message()); |
| } |
| return; |
| } |
| if (ctx->SIZE() != nullptr) { // Default size. |
| int value = std::stoi(ctx->NUMBER()->getText(), nullptr, 0); |
| slot->set_default_instruction_size(value); |
| return; |
| } |
| if (ctx->LATENCY() != nullptr) { // Default latency. |
| context_file_map_.insert({ctx->expression(), context_file_map_.at(ctx)}); |
| auto* expr = VisitExpression(ctx->expression(), slot, nullptr); |
| if (expr == nullptr) { |
| delete expr; |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| ctx->expression()->start, |
| "Error in expression"); |
| return; |
| } |
| slot->set_default_latency(expr); |
| return; |
| } |
| if (ctx->ATTRIBUTES() != nullptr) { // Default attributes. |
| context_file_map_.insert( |
| {ctx->instruction_attribute_list(), context_file_map_.at(ctx)}); |
| VisitInstructionAttributeList(ctx->instruction_attribute_list(), slot, |
| nullptr); |
| return; |
| } |
| // Add any include files to our set of includes. |
| if (ctx->include_file_list() != nullptr) { |
| for (auto* include_file : ctx->include_file_list()->include_file()) { |
| // Insert the string - the call will always succeed, but the insertion |
| // does not happen if it already exists. |
| include_files_.insert(include_file->STRING_LITERAL()->getText()); |
| } |
| } |
| if (ctx->OPCODE() != nullptr) { // Default opcode. |
| // Process the "default" instruction, which is used to specify disassembly |
| // and semantic function for when no valid opcode is found during decode. |
| if (slot->default_instruction() != nullptr) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| "Multiple definitions of 'default' opcode"); |
| return; |
| } |
| auto* default_instruction = new Instruction( |
| slot->instruction_set()->opcode_factory()->CreateDefaultOpcode(), slot); |
| bool has_disasm = false; |
| bool has_semfunc = false; |
| for (auto* attribute : ctx->opcode_attribute_list()->opcode_attribute()) { |
| // Disasm spec. |
| if (attribute->disasm_spec() != nullptr) { |
| if (has_disasm) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], attribute->start, |
| "Duplicate disasm declaration"); |
| continue; |
| } |
| has_disasm = true; |
| for (auto* format_str : attribute->disasm_spec()->STRING_LITERAL()) { |
| std::string format = format_str->getText(); |
| // Trim the double quotes. |
| format.erase(format.size() - 1, 1); |
| format.erase(0, 1); |
| auto status = ParseDisasmFormat(format, default_instruction); |
| if (!status.ok()) { |
| error_listener()->semanticError(attribute->disasm_spec()->start, |
| status.message()); |
| } |
| } |
| continue; |
| } |
| // Semfunc spec. |
| // If a semfunc has already been declared, signal an error. |
| if (has_semfunc) { |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| attribute->start, |
| "Duplicate semfunc declaration"); |
| continue; |
| } |
| has_semfunc = true; |
| auto semfunc_code = attribute->semfunc_spec()->STRING_LITERAL(); |
| // Only one semfunc specification (no child instructions) for default |
| // opcode. |
| if (semfunc_code.size() > 1) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| "Only one semfunc specification per default opcode"); |
| continue; |
| } |
| std::string string_literal = semfunc_code[0]->getText(); |
| // Strip double quotes. |
| std::string code_string = |
| string_literal.substr(1, string_literal.length() - 2); |
| default_instruction->set_semfunc_code_string(code_string); |
| } |
| if (has_semfunc) { |
| slot->set_default_instruction(default_instruction); |
| } else { |
| delete default_instruction; |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| "Default opcode lacks mandatory semfunc specification"); |
| } |
| } |
| if (ctx->resource_details() != nullptr) { |
| std::string ident = ctx->ident()->getText(); |
| if (slot->resource_spec_map().contains(ident)) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->ident()->start, |
| absl::StrCat("Resources '", ident, "': duplicate definition")); |
| return; |
| } |
| // Save the context. It will be re-visited at each point of use. |
| slot->resource_spec_map().insert( |
| std::make_pair(ident, ctx->resource_details())); |
| } |
| } |
| |
| // Visit the template argument recursively to create an expression tree that |
| // can be evaluated later. No need to coalesce constant expression trees, the |
| // savings aren't that great. |
| TemplateExpression* InstructionSetVisitor::VisitExpression(ExpressionCtx* ctx, |
| Slot* slot, |
| Instruction* inst) { |
| if (ctx == nullptr) return nullptr; |
| if (ctx->negop() != nullptr) { |
| context_file_map_.insert({ctx->expr, context_file_map_.at(ctx)}); |
| TemplateExpression* expr = VisitExpression(ctx->expr, slot, inst); |
| if (expr == nullptr) return nullptr; |
| return new TemplateNegate(expr); |
| } |
| |
| if (ctx->mulop() != nullptr) { |
| std::string op = ctx->mulop()->getText(); |
| context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)}); |
| TemplateExpression* lhs = VisitExpression(ctx->lhs, slot, inst); |
| if (lhs == nullptr) return nullptr; |
| context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)}); |
| TemplateExpression* rhs = VisitExpression(ctx->rhs, slot, inst); |
| if (rhs == nullptr) { |
| delete lhs; |
| return nullptr; |
| } |
| if (op == "*") return new TemplateMultiply(lhs, rhs); |
| return new TemplateDivide(lhs, rhs); |
| } |
| |
| if (ctx->addop() != nullptr) { |
| std::string op = ctx->addop()->getText(); |
| context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)}); |
| TemplateExpression* lhs = VisitExpression(ctx->lhs, slot, inst); |
| if (lhs == nullptr) return nullptr; |
| context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)}); |
| TemplateExpression* rhs = VisitExpression(ctx->rhs, slot, inst); |
| if (rhs == nullptr) { |
| delete lhs; |
| return nullptr; |
| } |
| if (op == "+") return new TemplateAdd(lhs, rhs); |
| return new TemplateSubtract(lhs, rhs); |
| } |
| |
| if (ctx->func != nullptr) { |
| std::string function = ctx->func->getText(); |
| auto iter = template_function_evaluators_.find(function); |
| if (iter == template_function_evaluators_.end()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| absl::StrCat("No function '", function, "' supported")); |
| return nullptr; |
| } |
| auto evaluator = iter->second; |
| if (ctx->expression().size() != evaluator.arity) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| absl::StrCat("Function '", function, "' takes ", evaluator.arity, |
| " parameters, but ", ctx->expression().size(), |
| " were given")); |
| } |
| auto* args = new TemplateInstantiationArgs; |
| bool has_error = false; |
| for (auto* expr_ctx : ctx->expression()) { |
| context_file_map_.insert({expr_ctx, context_file_map_.at(ctx)}); |
| auto* expr = VisitExpression(expr_ctx, slot, inst); |
| if (expr == nullptr) { |
| has_error = true; |
| break; |
| } |
| args->push_back(expr); |
| } |
| if (has_error) { |
| for (auto* expr : *args) { |
| delete expr; |
| } |
| delete args; |
| return nullptr; |
| } |
| return new TemplateFunction(evaluator.function, args); |
| } |
| |
| if (ctx->paren_expr != nullptr) { |
| context_file_map_.insert({ctx->paren_expr, context_file_map_.at(ctx)}); |
| return VisitExpression(ctx->paren_expr, slot, inst); |
| } |
| |
| if (ctx->NUMBER() != nullptr) { |
| auto* expr = |
| new TemplateConstant(std::stoi(ctx->NUMBER()->getText(), nullptr, 0)); |
| return expr; |
| } |
| |
| if (ctx->IDENT() != nullptr) { |
| std::string ident = ctx->IDENT()->getText(); |
| // Four possibilities. A global constant, a slot local constant, a |
| // template formal, or a reference to a destination operand. |
| if (slot != nullptr) { |
| TemplateFormal* param = slot->GetTemplateFormal(ident); |
| if (param != nullptr) return new TemplateParam(param); |
| |
| // Check if it's a slot const expression. |
| auto* expr = slot->GetConstExpression(ident); |
| if (expr != nullptr) return expr->DeepCopy(); |
| } |
| |
| // It should be an opcode operand term, which means it should be a |
| // destination operand with a latency. That is the value/expression that |
| // is needed here. |
| if (inst != nullptr) { |
| auto* op = inst->GetDestOp(ident); |
| if (op == nullptr) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| absl::StrCat("'", ident, |
| "' is not a valid destination operand for opcode '", |
| inst->opcode()->name(), "'")); |
| return nullptr; |
| } |
| |
| auto* expr = op->expression(); |
| if (expr != nullptr) return expr->DeepCopy(); |
| |
| // expr is null, this means that the destination operand has a decode |
| // time computed latency. This is a special case that will be addressed |
| // later. For now, signal an unsupported error. |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| ctx->start, |
| "Decode time evaluation of latency " |
| "expression not supported for resources"); |
| return nullptr; |
| } |
| |
| auto* expr = GetConstExpression(ident); |
| if (expr != nullptr) return expr->DeepCopy(); |
| |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| absl::StrCat("Unable to evaluate expression: ", "'", ctx->getText(), |
| "'")); |
| } |
| |
| return nullptr; |
| } |
| |
| DestinationOperand* InstructionSetVisitor::FindDestinationOpInExpression( |
| ExpressionCtx* ctx, const Slot* slot, const Instruction* inst) { |
| if (ctx == nullptr) return nullptr; |
| if (ctx->negop() != nullptr) { |
| context_file_map_.insert({ctx->expr, context_file_map_.at(ctx)}); |
| return FindDestinationOpInExpression(ctx->expr, slot, inst); |
| } |
| if ((ctx->mulop() != nullptr) || (ctx->addop() != nullptr)) { |
| context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)}); |
| context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)}); |
| auto* lhs = FindDestinationOpInExpression(ctx->lhs, slot, inst); |
| auto* rhs = FindDestinationOpInExpression(ctx->rhs, slot, inst); |
| if (lhs == nullptr) return rhs; |
| if (rhs == nullptr) return lhs; |
| if (lhs == rhs) return lhs; |
| |
| error_listener()->semanticError( |
| ctx->start, |
| "Resource reference can only reference a single " |
| "destination operand"); |
| return nullptr; |
| } |
| if (ctx->paren_expr != nullptr) { |
| context_file_map_.insert({ctx->paren_expr, context_file_map_.at(ctx)}); |
| return FindDestinationOpInExpression(ctx->paren_expr, slot, inst); |
| } |
| if (ctx->NUMBER() != nullptr) { |
| return nullptr; |
| } |
| if (ctx->func != nullptr) { |
| DestinationOperand* dest_op = nullptr; |
| DestinationOperand* tmp_op; |
| for (auto* expr_ctx : ctx->expression()) { |
| context_file_map_.insert({expr_ctx, context_file_map_.at(ctx)}); |
| tmp_op = FindDestinationOpInExpression(expr_ctx, slot, inst); |
| if (dest_op == nullptr) { |
| dest_op = tmp_op; |
| continue; |
| } |
| if (tmp_op != nullptr) { |
| if (dest_op != tmp_op) { |
| error_listener()->semanticError( |
| ctx->start, |
| "Resource reference can only reference a single " |
| "destination operand"); |
| } |
| } |
| } |
| return dest_op; |
| } |
| std::string ident = ctx->IDENT()->getText(); |
| // It is either a slot local constant, a template formal, or a reference |
| // to a destination operand. |
| TemplateFormal* param = slot->GetTemplateFormal(ident); |
| if (param != nullptr) return nullptr; |
| // Check if it's a slot const expression. |
| auto* expr = slot->GetConstExpression(ident); |
| if (expr != nullptr) return nullptr; |
| // It should be an opcode operand term. |
| return inst->GetDestOp(ident); |
| } |
| |
| void InstructionSetVisitor::VisitOpcodeList(OpcodeListCtx* ctx, Slot* slot) { |
| absl::flat_hash_set<std::string> deleted_ops_set; |
| absl::flat_hash_set<OpcodeSpecCtx*> overridden_ops_set; |
| std::vector<Instruction*> instruction_vec; |
| if (ctx != nullptr) { |
| ProcessOpcodeList(ctx, slot, instruction_vec, deleted_ops_set, |
| overridden_ops_set); |
| } |
| // For all base slots, and all opcodes that aren't excluded, add the opcodes |
| // to the current slot. When adding the instruction, pass in any template |
| // instantiation arguments to the base slot so that any expressions for |
| // destination operand latencies can be evaluated. |
| for (auto const& base_slot : slot->base_slots()) { |
| if (base_slot.base->min_instruction_size() < slot->min_instruction_size()) { |
| slot->set_min_instruction_size(base_slot.base->min_instruction_size()); |
| } |
| // Copy over the instructions that were not deleted. |
| for (auto& [unused, inst_ptr] : base_slot.base->instruction_map()) { |
| if (!deleted_ops_set.contains(inst_ptr->opcode()->name())) { |
| absl::Status status = |
| slot->AppendInheritedInstruction(inst_ptr, base_slot.arguments); |
| if (!status.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(ctx)], ctx->start, |
| status.message()); |
| } |
| } |
| } |
| // Perform the overrides. |
| PerformOpcodeOverrides(overridden_ops_set, slot); |
| } |
| // Add the declared opcodes. |
| for (auto* inst : instruction_vec) { |
| absl::Status status = slot->AppendInstruction(inst); |
| if (!status.ok()) { |
| error_listener()->semanticError(file_names_[context_file_map_.at(ctx)], |
| ctx->start, status.message()); |
| } |
| } |
| } |
| |
| void InstructionSetVisitor::PerformOpcodeOverrides( |
| absl::flat_hash_set<OpcodeSpecCtx*> overridden_ops_set, Slot* slot) { |
| for (auto* override_ctx : overridden_ops_set) { |
| std::string name = override_ctx->name->getText(); |
| auto* inst = slot->instruction_map().at(name); |
| VisitOpcodeAttributes(override_ctx->opcode_attribute_list(), inst, slot); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitOpcodeAttributes(OpcodeAttributeListCtx* ctx, |
| Instruction* inst, |
| Slot* slot) { |
| if (ctx == nullptr) return; |
| // These flags are used to detect multiple instances of each attribute. |
| bool has_disasm = false; |
| bool has_semfunc = false; |
| bool has_resources = false; |
| bool has_attributes = false; |
| // Visit the opcode attributes. |
| for (auto* attribute_ctx : ctx->opcode_attribute()) { |
| // Process any disassembly specifications. |
| if (attribute_ctx->disasm_spec() != nullptr) { |
| // In case of override, need to clear any disasm info in instruction. |
| inst->ClearDisasmFormat(); |
| // Signal error if there is more than one disassembly spec. |
| if (has_disasm) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| attribute_ctx->start, "Multiple disasm specifications"); |
| continue; |
| } |
| has_disasm = true; |
| for (auto* disasm_fmt : attribute_ctx->disasm_spec()->STRING_LITERAL()) { |
| std::string format = disasm_fmt->getText(); |
| // Trim the double quotes. |
| format.erase(format.size() - 1, 1); |
| format.erase(0, 1); |
| auto status = ParseDisasmFormat(format, inst); |
| if (!status.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| attribute_ctx->disasm_spec()->start, status.message()); |
| has_disasm = false; |
| break; |
| } |
| } |
| continue; |
| } |
| |
| // Process the semantic function specification. |
| if (attribute_ctx->semfunc_spec() != nullptr) { |
| // In case of override, need to clear the semantic function string. |
| inst->ClearSemfuncCodeString(); |
| // Signal error if there is more than one semantic function spec. |
| if (has_semfunc) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| attribute_ctx->start, "Multiple semfunc specifications"); |
| continue; |
| } |
| has_semfunc = true; |
| VisitSemfuncSpec(attribute_ctx->semfunc_spec(), inst); |
| continue; |
| } |
| |
| // Process resource specification. |
| if (attribute_ctx->resource_spec() != nullptr) { |
| // In case of override, need to clear the resource specifications. |
| inst->ClearResourceSpecs(); |
| // Signal error if there is more than one resource specification. |
| if (has_resources) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| attribute_ctx->start, "Multiple resource specifications"); |
| continue; |
| } |
| has_resources = true; |
| VisitResourceDetails(attribute_ctx->resource_spec()->resource_details(), |
| inst, slot); |
| continue; |
| } |
| |
| // Process instruction attribute specification. |
| if (attribute_ctx->instruction_attribute_spec() != nullptr) { |
| // In case of override, need to clear the attribute specification. |
| inst->ClearAttributeSpecs(); |
| // Signal error if there is more than one attribute specification. |
| if (has_attributes) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| attribute_ctx->start, "Multiple attribute specifications"); |
| continue; |
| } |
| has_attributes = true; |
| auto attr_list_ctx = attribute_ctx->instruction_attribute_spec() |
| ->instruction_attribute_list(); |
| VisitInstructionAttributeList(attr_list_ctx, slot, inst); |
| continue; |
| } |
| |
| // Unknown attribute type. |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], attribute_ctx->start, |
| "Unknown attribute type"); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitInstructionAttributeList( |
| InstructionAttributeListCtx* ctx, Slot* slot, Instruction* inst) { |
| absl::flat_hash_map<std::string, TemplateExpression*> attributes; |
| for (auto* attribute : ctx->instruction_attribute()) { |
| std::string name = attribute->IDENT()->getText(); |
| if (attributes.find(name) != attributes.end()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], attribute->start, |
| absl::StrCat("Duplicate attribute name '", name, "' in list")); |
| continue; |
| } |
| slot->AddAttributeName(name); |
| if (attribute->expression() != nullptr) { |
| context_file_map_.insert( |
| {attribute->expression(), context_file_map_.at(slot->ctx())}); |
| auto* expr = VisitExpression(attribute->expression(), slot, inst); |
| attributes.emplace(name, expr); |
| continue; |
| } |
| attributes.emplace(name, new TemplateConstant(0)); |
| } |
| // Are we parsing attributes for an instruction? |
| if (inst != nullptr) { |
| for (auto* child = inst; child != nullptr; child = child->child()) { |
| for (auto& [name, expr] : attributes) { |
| child->AddInstructionAttribute( |
| name, expr == nullptr ? nullptr : expr->DeepCopy()); |
| } |
| } |
| // Ownership of expr objects transferred to opcode. |
| for (auto& [unused, expr] : attributes) { |
| delete expr; |
| } |
| attributes.clear(); |
| return; |
| } |
| // Attributes are default attributes for the current slot. |
| for (auto& [name, expr] : attributes) { |
| slot->AddInstructionAttribute(name, |
| expr == nullptr ? nullptr : expr->DeepCopy()); |
| } |
| for (auto& [unused, expr] : attributes) { |
| delete expr; |
| } |
| attributes.clear(); |
| } |
| |
| void InstructionSetVisitor::VisitSemfuncSpec(SemfuncSpecCtx* semfunc_spec, |
| Instruction* inst) { |
| auto* child = inst; |
| // Parse each string in the list of semantic function specifications. There |
| // should be one the opcode and one for each child opcode. |
| for (auto* sem_func : semfunc_spec->STRING_LITERAL()) { |
| if (child == nullptr) { |
| error_listener()->semanticWarning( |
| file_names_[context_file_map_.at(inst->slot()->ctx())], |
| sem_func->getSymbol(), "Ignoring extra semfunc spec"); |
| break; |
| } |
| std::string literal = sem_func->getText(); |
| std::string code_string = literal.substr(1, literal.length() - 2); |
| child->set_semfunc_code_string(code_string); |
| child = child->child(); |
| } |
| // Are there fewer specifier strings than child instructions? |
| if (child != nullptr) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(inst->slot()->ctx())], |
| semfunc_spec->start, |
| absl::StrCat("Fewer semfunc specifiers than expected for opcode '", |
| inst->opcode()->name(), "'")); |
| } |
| } |
| |
| void InstructionSetVisitor::VisitResourceDetails(ResourceDetailsCtx* ctx, |
| Instruction* inst, |
| Slot* slot) { |
| if (ctx->ident() != nullptr) { |
| // This is a reference to a resource spec defined earlier. |
| std::string name = ctx->ident()->getText(); |
| auto iter = slot->resource_spec_map().find(name); |
| if (iter == slot->resource_spec_map().end()) { |
| // This should never happen. |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], ctx->start, |
| absl::StrCat("Internal error: Undefined resources name: '", name, |
| "'")); |
| return; |
| } |
| ctx = iter->second; |
| } |
| ResourceSpec spec; |
| VisitResourceDetailsLists(ctx, slot, inst, &spec); |
| for (auto* use : spec.use_vec) { |
| inst->AppendResourceUse(use); |
| } |
| for (auto* acquire : spec.acquire_vec) { |
| inst->AppendResourceAcquire(acquire); |
| } |
| } |
| |
| std::optional<ResourceReference*> |
| InstructionSetVisitor::ProcessResourceReference( |
| Slot* slot, Instruction* inst, ResourceItemCtx* resource_item) { |
| // Empty optional object. |
| std::optional<ResourceReference*> return_value; |
| |
| auto* factory = slot->instruction_set()->resource_factory(); |
| DestinationOperand* dest_op = nullptr; |
| // Extract the text from the resource reference. |
| std::string ident_text; |
| bool is_array; |
| if (resource_item->name != nullptr) { |
| ident_text = resource_item->name->getText(); |
| is_array = false; |
| } else { |
| // Prepend the name with [] so that it doesn't conflict with a non-array |
| // resource of the same name. |
| ident_text = resource_item->array_name->getText(); |
| is_array = true; |
| } |
| dest_op = inst->GetDestOp(ident_text); |
| auto* resource = factory->GetOrInsertResource(ident_text); |
| resource->set_is_array(is_array); |
| // Compute begin and end values. |
| TemplateExpression* begin_expr; |
| TemplateExpression* end_expr; |
| DestinationOperand* tmp_op; |
| if (resource_item->begin_cycle == nullptr) { |
| begin_expr = new TemplateConstant(0); |
| } else { |
| context_file_map_.insert( |
| {resource_item->begin_cycle, context_file_map_.at(slot->ctx())}); |
| tmp_op = |
| FindDestinationOpInExpression(resource_item->begin_cycle, slot, inst); |
| if (tmp_op != nullptr) { |
| if (dest_op == nullptr) { |
| dest_op = tmp_op; |
| } else if (dest_op != tmp_op) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| resource_item->start, |
| "Resource reference can only reference a single " |
| "destination operand"); |
| return return_value; |
| } |
| } |
| context_file_map_.insert( |
| {resource_item->begin_cycle, context_file_map_.at(slot->ctx())}); |
| begin_expr = VisitExpression(resource_item->begin_cycle, slot, inst); |
| } |
| |
| if (resource_item->end_cycle == nullptr) { |
| // If there is no end_cycle specified, then it inherits from the dest_op |
| // if available. |
| // TODO(torerik): Handle case where the dest latency is '*'. |
| if ((dest_op != nullptr) && (dest_op->expression() != nullptr)) { |
| end_expr = dest_op->expression()->DeepCopy(); |
| } else { |
| end_expr = new TemplateConstant(0); |
| } |
| } else { |
| context_file_map_.insert( |
| {resource_item->end_cycle, context_file_map_.at(slot->ctx())}); |
| tmp_op = |
| FindDestinationOpInExpression(resource_item->end_cycle, slot, inst); |
| if (tmp_op != nullptr) { |
| if (dest_op == nullptr) { |
| dest_op = tmp_op; |
| } else if (dest_op != tmp_op) { |
| error_listener()->semanticError( |
| resource_item->start, |
| "Resource reference can only reference a single " |
| "destination operand"); |
| delete begin_expr; |
| return return_value; |
| } |
| } |
| context_file_map_.insert( |
| {resource_item->end_cycle, context_file_map_.at(slot->ctx())}); |
| end_expr = VisitExpression(resource_item->end_cycle, slot, inst); |
| } |
| auto* ref = |
| new ResourceReference(resource, is_array, dest_op, begin_expr, end_expr); |
| return std::optional<ResourceReference*>(ref); |
| } |
| |
| void InstructionSetVisitor::VisitResourceDetailsLists(ResourceDetailsCtx* ctx, |
| Slot* slot, |
| Instruction* inst, |
| ResourceSpec* spec) { |
| if (ctx == nullptr) return; |
| |
| if ((ctx->use_list == nullptr) && (ctx->acquire_list == nullptr) && |
| (ctx->hold_list == nullptr)) |
| return; |
| |
| // The resource details consists of three lists: use, acquire, and |
| // hold. The "use" list specifies the resources that have to only be |
| // available at instruction dispatch time. The "acquire" list specifies the |
| // list of resources that the instruction must be available to acquire, and |
| // hold for a certain set of cycles. The hold list specifies the list of |
| // resources that will be marked held (regardless of their status) for the |
| // set of cycles specified. |
| |
| // Use list. |
| auto* use_list = ctx->use_list; |
| if (use_list != nullptr) { |
| for (auto* resource_item : use_list->resource_item()) { |
| auto ref_optional = ProcessResourceReference(slot, inst, resource_item); |
| if (!ref_optional) continue; |
| spec->use_vec.push_back(ref_optional.value()); |
| } |
| } |
| |
| // Reserve list. |
| auto* acquire_list = ctx->acquire_list; |
| if (acquire_list != nullptr) { |
| for (auto* resource_item : acquire_list->resource_item()) { |
| auto ref_optional = ProcessResourceReference(slot, inst, resource_item); |
| if (!ref_optional) continue; |
| // Only add to use_vec if it isn't already there. |
| bool found = false; |
| for (auto* ref : spec->use_vec) { |
| if (ref->resource->name() == ref_optional.value()->resource->name()) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| spec->use_vec.push_back(new ResourceReference(*ref_optional.value())); |
| } |
| spec->acquire_vec.push_back(ref_optional.value()); |
| } |
| } |
| |
| // Hold list. |
| auto* hold_list = ctx->hold_list; |
| if (hold_list != nullptr) { |
| for (auto* resource_item : hold_list->resource_item()) { |
| auto ref_optional = ProcessResourceReference(slot, inst, resource_item); |
| if (!ref_optional) continue; |
| spec->acquire_vec.push_back(ref_optional.value()); |
| } |
| } |
| } |
| |
| void InstructionSetVisitor::ProcessOpcodeList( |
| OpcodeListCtx* ctx, Slot* slot, std::vector<Instruction*>& instruction_vec, |
| absl::flat_hash_set<std::string>& deleted_ops_set, |
| absl::flat_hash_set<OpcodeSpecCtx*>& overridden_ops_set) { |
| // Obtain the list of opcodes specifications. |
| auto opcode_spec = ctx->opcode_spec(); |
| for (auto* opcode_ctx : opcode_spec) { |
| ProcessOpcodeSpec(opcode_ctx, slot, instruction_vec, deleted_ops_set, |
| overridden_ops_set); |
| } |
| } |
| |
| void InstructionSetVisitor::ProcessOpcodeSpec( |
| OpcodeSpecCtx* opcode_ctx, Slot* slot, |
| std::vector<Instruction*>& instruction_vec, |
| absl::flat_hash_set<std::string>& deleted_ops_set, |
| absl::flat_hash_set<OpcodeSpecCtx*>& overridden_ops_set) { |
| auto* opcode_factory = slot->instruction_set()->opcode_factory(); |
| if (opcode_ctx->generate != nullptr) { |
| auto status = ProcessOpcodeGenerator(opcode_ctx, slot, instruction_vec, |
| deleted_ops_set, overridden_ops_set); |
| if (!status.ok()) { |
| error_listener()->semanticError(opcode_ctx->name, status.message()); |
| } |
| return; |
| } |
| // Process the regular opcode specification. |
| std::string opcode_name = opcode_ctx->name->getText(); |
| // Check to see if this opcode is deleted, meaning it should not be |
| // inherited from a base slot. |
| if (opcode_ctx->deleted != nullptr) { |
| // If there is no base slot, this is an error. |
| if (slot->base_slots().empty()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted, |
| absl::StrCat("Invalid deleted opcode '", opcode_name, "', slot '", |
| slot->name(), "' does not inherit from a base slot")); |
| return; |
| } |
| bool found = false; |
| // Check to see if one of the base slots has this opcode. |
| for (auto const& base_slot : slot->base_slots()) { |
| found |= base_slot.base->HasInstruction(opcode_name); |
| if (found) break; |
| } |
| // If the opcode was not defined in any of the base slots, it is an error. |
| if (!found) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted, |
| absl::StrCat("Base slot does not define or inherit opcode '", |
| opcode_name, "'")); |
| return; |
| } |
| deleted_ops_set.insert(opcode_name); |
| return; |
| } |
| |
| // Check to see if this opcode is overridden, this means that some of the |
| // "attributes" (semantic function, disasm, etc.) are changed. |
| if (opcode_ctx->overridden != nullptr) { |
| int found = 0; |
| for (auto const& base_slot : slot->base_slots()) { |
| found += base_slot.base->HasInstruction(opcode_name); |
| } |
| // Check that the opcode is indeed inherited from one base class only. |
| // Multiple inheritance is not supported. |
| if (found == 0) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted, |
| absl::StrCat("Base slot does not define or inherit opcode '", |
| opcode_name, "'")); |
| return; |
| } else if (found > 1) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted, |
| absl::StrCat("Multiple inheritance of opcodes is not supported: ", |
| opcode_name)); |
| return; |
| } |
| overridden_ops_set.insert(opcode_ctx); |
| return; |
| } |
| |
| // This is a new opcode, so let's create it. Signal failure if error. |
| absl::StatusOr<Opcode*> result = opcode_factory->CreateOpcode(opcode_name); |
| if (!result.ok()) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->name, |
| result.status().message()); |
| return; |
| } |
| |
| Opcode* top = result.value(); |
| auto inst = new Instruction(top, slot); |
| slot->instruction_set()->AddInstruction(inst); |
| |
| // Get the size of the instruction if specified, otherwise use default size. |
| if (opcode_ctx->size_spec() != nullptr) { |
| int size = |
| std::stoi(opcode_ctx->size_spec()->NUMBER()->getText(), nullptr, 0); |
| top->set_instruction_size(size); |
| } else { |
| top->set_instruction_size(slot->default_instruction_size()); |
| } |
| if (top->instruction_size() < slot->min_instruction_size()) { |
| slot->set_min_instruction_size(top->instruction_size()); |
| } |
| |
| int op_spec_number = 0; |
| auto op_spec = opcode_ctx->operand_spec(); |
| // Process the top instruction. |
| for (auto& [name, expr] : slot->attribute_map()) { |
| inst->AddInstructionAttribute(name, expr->DeepCopy()); |
| } |
| |
| // Visit the opcode specification of the top instruction. |
| if (op_spec->opcode_operands() != nullptr) { |
| VisitOpcodeOperands(op_spec->opcode_operands(), op_spec_number, inst, inst, |
| slot); |
| } else { |
| VisitOpcodeOperands(op_spec->opcode_operands_list()->opcode_operands()[0], |
| op_spec_number, inst, inst, slot); |
| } |
| op_spec_number++; |
| |
| // If there are child instructions process them. |
| if (opcode_ctx->operand_spec()->opcode_operands_list() != nullptr) { |
| Opcode* parent = top; |
| auto opcode_operands = op_spec->opcode_operands_list()->opcode_operands(); |
| |
| Instruction* child_inst = nullptr; |
| // Process child instructions. |
| for (size_t i = 1; i < opcode_operands.size(); ++i) { |
| // Create child opcode. |
| auto* op = opcode_factory->CreateChildOpcode(parent); |
| // Create child instruction. |
| child_inst = new Instruction(op, slot); |
| inst->AppendChild(child_inst); |
| // Add default attributes. |
| for (auto& [name, expr] : slot->attribute_map()) { |
| child_inst->AddInstructionAttribute(name, expr->DeepCopy()); |
| } |
| VisitOpcodeOperands(opcode_operands[i], op_spec_number, inst, child_inst, |
| slot); |
| op_spec_number++; |
| } |
| } |
| instruction_vec.push_back(inst); |
| VisitOpcodeAttributes(opcode_ctx->opcode_attribute_list(), inst, slot); |
| } |
| |
| void InstructionSetVisitor::VisitOpcodeOperands(OpcodeOperandsCtx* ctx, |
| int op_spec_number, |
| Instruction* parent, |
| Instruction* child, |
| Slot* slot) { |
| if (ctx == nullptr) return; |
| if (ctx->pred != nullptr) { |
| std::string name = ctx->pred->getText(); |
| child->opcode()->set_predicate_op_name(name); |
| parent->opcode()->op_locator_map().insert(std::make_pair( |
| name, OperandLocator(op_spec_number, 'p', /*is_reloc=*/false, |
| /*instance=*/0))); |
| } |
| if (ctx->source != nullptr) { |
| int instance = 0; |
| for (auto* source_op : ctx->source->source_operand()) { |
| std::string name; |
| bool is_array = false; |
| bool is_reloc = false; |
| if (source_op->operand() != nullptr) { |
| name = source_op->operand()->op_name->getText(); |
| if (source_op->operand()->op_attribute != nullptr) { |
| auto attr = source_op->operand()->op_attribute->getText(); |
| if (attr == "%reloc") { |
| is_reloc = true; |
| } else { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| source_op->operand()->op_attribute, |
| absl::StrCat("Invalid operand attribute '", attr, "'")); |
| } |
| } |
| } else { |
| name = source_op->array_source->getText(); |
| is_array = true; |
| } |
| child->opcode()->AppendSourceOp(name, is_array, is_reloc); |
| parent->opcode()->op_locator_map().insert(std::make_pair( |
| name, OperandLocator(op_spec_number, is_array ? 't' : 's', is_reloc, |
| instance))); |
| instance++; |
| } |
| } |
| if (ctx->dest_list() != nullptr) { |
| int instance = 0; |
| for (auto* dest_op : ctx->dest_list()->dest_operand()) { |
| std::string ident; |
| bool is_array = false; |
| bool is_reloc = false; |
| if (dest_op->operand() != nullptr) { |
| ident = dest_op->operand()->op_name->getText(); |
| if (dest_op->operand()->op_attribute != nullptr) { |
| auto attr = dest_op->operand()->op_attribute->getText(); |
| if (attr == "%reloc") { |
| is_reloc = true; |
| } else { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| dest_op->operand()->op_attribute, |
| absl::StrCat("Invalid operand attribute '", attr, "'")); |
| } |
| } |
| } else { |
| ident = dest_op->array_dest->getText(); |
| is_array = true; |
| } |
| // The latency of the destination operand is either specified by an |
| // expression, by '*' (wildcard), or omitted, in which case it |
| // defaults to 1. |
| if (dest_op->expression() != nullptr) { |
| context_file_map_.insert( |
| {dest_op->expression(), context_file_map_.at(slot->ctx())}); |
| child->opcode()->AppendDestOp( |
| ident, is_array, is_reloc, |
| VisitExpression(dest_op->expression(), slot, child)); |
| } else if (dest_op->wildcard != nullptr) { |
| child->opcode()->AppendDestOp(ident, is_array, is_reloc); |
| } else if (slot->default_latency() != nullptr) { |
| child->opcode()->AppendDestOp(ident, is_array, is_reloc, |
| slot->default_latency()->DeepCopy()); |
| } else { |
| child->opcode()->AppendDestOp(ident, is_array, is_reloc, |
| new TemplateConstant(1)); |
| } |
| parent->opcode()->op_locator_map().insert(std::make_pair( |
| ident, OperandLocator(op_spec_number, is_array ? 'e' : 'd', is_reloc, |
| instance))); |
| instance++; |
| } |
| } |
| } |
| |
| absl::Status InstructionSetVisitor::ProcessOpcodeGenerator( |
| OpcodeSpecCtx* ctx, Slot* slot, std::vector<Instruction*>& instruction_vec, |
| absl::flat_hash_set<std::string>& deleted_ops_set, |
| absl::flat_hash_set<OpcodeSpecCtx*>& overridden_ops_set) { |
| if (ctx == nullptr) return absl::InternalError("OpcodeSpecCtx is null"); |
| absl::flat_hash_set<std::string> range_variable_names; |
| std::vector<RangeAssignmentInfo*> range_info_vec; |
| // Process range assignment lists. The range assignment is either a single |
| // value or a structured binding assignment. If it's a binding assignment we |
| // need to make sure each tuple has the same number of values as there are |
| // idents to assign them to. |
| for (auto* assign_ctx : ctx->range_assignment()) { |
| auto* range_info = new RangeAssignmentInfo(); |
| range_info_vec.push_back(range_info); |
| for (auto* ident_ctx : assign_ctx->IDENT()) { |
| std::string name = ident_ctx->getText(); |
| if (range_variable_names.contains(name)) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], assign_ctx->start, |
| absl::StrCat("Duplicate binding variable name '", name, "'")); |
| continue; |
| } |
| range_variable_names.insert(name); |
| range_info->range_names.push_back(ident_ctx->getText()); |
| range_info->range_values.push_back({}); |
| range_info->range_regexes.emplace_back( |
| absl::StrCat("\\$\\(", name, "\\)")); |
| // Verify that the range variable is used in the string. |
| if (!RE2::PartialMatch(ctx->generator_opcode_spec_list()->getText(), |
| range_info->range_regexes.back())) { |
| error_listener()->semanticWarning( |
| file_names_[context_file_map_.at(slot->ctx())], assign_ctx->start, |
| absl::StrCat("Unreferenced binding variable '", name, "'")); |
| } |
| } |
| // See if it's a list of simple values. |
| if (!assign_ctx->gen_value().empty()) { |
| for (auto* gen_value_ctx : assign_ctx->gen_value()) { |
| if (gen_value_ctx->simple != nullptr) { |
| range_info->range_values[0].push_back( |
| gen_value_ctx->simple->getText()); |
| } else { |
| // Strip off double quotes. |
| std::string value = gen_value_ctx->string->getText(); |
| range_info->range_values[0].push_back( |
| value.substr(1, value.size() - 2)); |
| } |
| } |
| continue; |
| } |
| // It's a list of tuples with a structured binding assignment. |
| for (auto* tuple_ctx : assign_ctx->value_list()) { |
| if (tuple_ctx->gen_value().size() != range_info->range_names.size()) { |
| for (auto* info : range_info_vec) delete info; |
| return absl::InternalError( |
| "Number of values differs from number of identifiers"); |
| } |
| for (int i = 0; i < tuple_ctx->gen_value().size(); ++i) { |
| if (tuple_ctx->gen_value(i)->simple != nullptr) { |
| range_info->range_values[i].push_back( |
| tuple_ctx->gen_value(i)->simple->getText()); |
| } else { |
| // Strip off double quotes. |
| std::string value = tuple_ctx->gen_value(i)->string->getText(); |
| range_info->range_values[i].push_back( |
| value.substr(1, value.size() - 2)); |
| } |
| } |
| } |
| } |
| // Check that all binding variable references are valid. |
| std::string input_text = ctx->generator_opcode_spec_list()->getText(); |
| std::string::size_type start_pos = 0; |
| auto pos = input_text.find_first_of('$', start_pos); |
| while (pos != std::string::npos) { |
| // Skip past the '$(' |
| start_pos = pos + 2; |
| auto end_pos = input_text.find_first_of(')', pos); |
| // Extract the ident. |
| auto ident = input_text.substr(start_pos, end_pos - start_pos); |
| if (!range_variable_names.contains(ident)) { |
| error_listener()->semanticError( |
| file_names_[context_file_map_.at(slot->ctx())], |
| ctx->generator_opcode_spec_list()->start, |
| absl::StrCat("Undefined binding variable '", ident, "'")); |
| } |
| start_pos = end_pos; |
| pos = input_text.find_first_of('$', start_pos); |
| } |
| if (error_listener()->HasError()) { |
| for (auto* info : range_info_vec) delete info; |
| return absl::InternalError("Found undefined binding variable name(s)"); |
| } |
| // Now we need to iterate over the range_info instances and substitution |
| // ranges. This will produce new text that will be parsed and processed. |
| std::string generated_text = |
| GenerateOpcodeSpec(range_info_vec, 0, input_text); |
| // Parse and process the generated text. |
| auto* parser = new IsaAntlrParserWrapper(generated_text); |
| antlr_parser_wrappers_.push_back(parser); |
| // Parse the text starting at the opcode_spec_list rule. |
| auto opcode_spec_vec = parser->parser()->opcode_spec_list()->opcode_spec(); |
| // Process the opcode spec. |
| for (auto* opcode_spec : opcode_spec_vec) { |
| ProcessOpcodeSpec(opcode_spec, slot, instruction_vec, deleted_ops_set, |
| overridden_ops_set); |
| } |
| // Clean up. |
| for (auto* info : range_info_vec) delete info; |
| return absl::OkStatus(); |
| } |
| |
| // Helper function to recursively generate the text for the GENERATE opcode |
| // spec. |
| std::string InstructionSetVisitor::GenerateOpcodeSpec( |
| const std::vector<RangeAssignmentInfo*>& range_info_vec, int index, |
| const std::string& template_str_in) const { |
| std::string generated; |
| // Iterate for the number of values. |
| for (int i = 0; i < range_info_vec[index]->range_values[0].size(); ++i) { |
| // Copy the template string. |
| std::string template_str = template_str_in; |
| // For each ident, perform substitutions in the template copy with the |
| // current set of values. |
| int var_index = 0; |
| int replace_count = 0; |
| for (auto& re : range_info_vec[index]->range_regexes) { |
| replace_count += RE2::GlobalReplace( |
| &template_str, re, |
| range_info_vec[index]->range_values[var_index++][i]); |
| } |
| // If there are multiple range specifications, then recursively call to |
| // generate the cartesian product with the values of the next value range |
| // substitutions. |
| if (range_info_vec.size() > index + 1) { |
| absl::StrAppend(&generated, GenerateOpcodeSpec(range_info_vec, index + 1, |
| template_str)); |
| } else { |
| absl::StrAppend(&generated, template_str); |
| } |
| // If there were no replacements, then the range variables weren't used, |
| // and the template string won't change for any other values in the range. |
| // This can happen if the range variables aren't referenced in the string. |
| // Thus, break out of the loop. |
| if (replace_count == 0) break; |
| } |
| return generated; |
| } |
| |
| static absl::StatusOr<std::pair<std::string, std::string::size_type>> get_ident( |
| std::string str, std::string::size_type pos) { |
| // If the next character is not an alpha or '_' it's an error. |
| if (!std::isalpha(str[pos]) && (str[pos] != '_')) { |
| return absl::InternalError( |
| absl::StrCat("Invalid character in operand name at position ", pos, |
| " in '", str, "'")); |
| } |
| std::string op_name; |
| while (std::isalnum(str[pos]) || (str[pos] == '_')) { |
| op_name.push_back(str[pos]); |
| pos++; |
| if (pos >= str.size()) { |
| pos = std::string::npos; |
| break; |
| } |
| } |
| return std::make_pair(op_name, pos); |
| } |
| // This method parses the disasm format string. |
| absl::Status InstructionSetVisitor::ParseDisasmFormat(std::string format, |
| Instruction* inst) { |
| std::string::size_type pos = 0; |
| std::string::size_type prev = 0; |
| std::string::size_type length = format.size(); |
| FormatInfo* format_info = nullptr; |
| // Extract raw text without (between) the '%' specifiers. |
| DisasmFormat* disasm_fmt = new DisasmFormat(); |
| while ((pos != std::string::npos) && |
| ((pos = format.find_first_of('%', pos)) != std::string::npos)) { |
| std::string text = format.substr(prev, pos - prev); |
| std::string new_text; |
| for (auto& c : text) { |
| if (c == '\\') continue; |
| new_text.push_back(c); |
| } |
| disasm_fmt->format_fragment_vec.push_back(new_text); |
| pos++; |
| if (pos >= length) break; |
| |
| // See if it is a simple %opname specifier or an expression. |
| if (format[pos] == '(') { // This is an expression. |
| pos++; |
| if (pos >= length) break; |
| |
| // Find end of the expression. |
| std::string::size_type end_pos = pos; |
| int paren_count = 0; |
| while (true) { |
| if (end_pos >= length) break; |
| if (format[end_pos] == ':') break; |
| if (format[end_pos] == '(') paren_count++; |
| if (format[end_pos] == ')') { |
| if (paren_count == 0) break; |
| paren_count--; |
| } |
| end_pos++; |
| } |
| |
| if (end_pos >= length) break; |
| |
| auto res = ParseFormatExpression(format.substr(pos, end_pos - pos), |
| inst->opcode()); |
| if (!res.ok()) { |
| delete disasm_fmt; |
| return res.status(); |
| } |
| |
| format_info = res.value(); |
| |
| pos = end_pos; |
| |
| format_info->number_format = "%d"; // Default number format. |
| |
| if (format[pos] == ':') { |
| pos++; |
| if (pos >= length) break; |
| |
| end_pos = format.find_first_of(')', pos); |
| if (end_pos == std::string::npos) break; |
| auto res = ParseNumberFormat(format.substr(pos, end_pos - pos)); |
| if (!res.ok()) { |
| delete format_info; |
| delete disasm_fmt; |
| return res.status(); |
| } |
| format_info->number_format = res.value(); |
| pos = end_pos; |
| } |
| pos++; |
| if (pos >= format.size()) { |
| pos = std::string::npos; |
| } else if (format[pos] == '?') { |
| format_info->is_optional = true; |
| pos++; |
| if (pos >= format.size()) { |
| pos = std::string::npos; |
| } |
| } |
| format_info->is_formatted = true; |
| disasm_fmt->format_info_vec.push_back(format_info); |
| |
| } else { // Simple %opname specifier. |
| auto res = get_ident(format, pos); |
| if (!res.ok()) { |
| delete disasm_fmt; |
| return res.status(); |
| } |
| auto [op_name, end_pos] = res.value(); |
| pos = end_pos; |
| if (!inst->opcode()->op_locator_map().contains(op_name)) { |
| delete disasm_fmt; |
| return absl::InternalError(absl::StrCat( |
| "Invalid operand '", op_name, "' used in format '", format, "'")); |
| } |
| auto* format_info = new FormatInfo(); |
| format_info->op_name = op_name; |
| format_info->is_formatted = false; |
| if ((pos != std::string::npos) && (format[pos] == '?')) { |
| format_info->is_optional = true; |
| disasm_fmt->num_optional++; |
| pos++; |
| if (pos >= format.size()) { |
| pos = std::string::npos; |
| } |
| } |
| disasm_fmt->format_info_vec.push_back(format_info); |
| } |
| |
| prev = pos; |
| } |
| if (pos != std::string::npos) { |
| delete format_info; |
| delete disasm_fmt; |
| return absl::InternalError( |
| absl::StrCat("Unexpected end of format string in '", format, "'")); |
| } |
| if (prev != std::string::npos) { |
| std::string text = format.substr(prev); |
| std::string new_text; |
| for (auto& c : text) { |
| if (c == '\\') continue; |
| new_text.push_back(c); |
| } |
| disasm_fmt->format_fragment_vec.push_back(new_text); |
| } |
| std::string str; |
| for (auto& s : disasm_fmt->format_fragment_vec) { |
| absl::StrAppend(&str, s, ":"); |
| } |
| int width = 0; |
| auto count = inst->disasm_format_vec().size(); |
| if (count < disasm_field_widths_.size()) { |
| auto result = disasm_field_widths_[count]->GetValue(); |
| if (result.ok()) { |
| auto* value_ptr = std::get_if<int>(&result.value()); |
| width = *value_ptr; |
| } |
| } |
| disasm_fmt->width = width; |
| inst->AppendDisasmFormat(disasm_fmt); |
| return absl::OkStatus(); |
| } |
| |
| static std::string::size_type skip_space(const std::string& str, |
| std::string::size_type pos) { |
| if (pos == std::string::npos) return pos; |
| if (pos >= str.size()) { |
| return std::string::npos; |
| } |
| while ((str[pos] == ' ') || (str[pos] == '\t')) { |
| pos++; |
| if (pos >= str.size()) return std::string::npos; |
| } |
| return pos; |
| } |
| |
| absl::StatusOr<FormatInfo*> InstructionSetVisitor::ParseFormatExpression( |
| std::string expr, Opcode* op) { |
| // The format expression is very simple. It is of the form: |
| // [@+/-] ident | '(' ident <</>> number ')' |
| // where @ signifies the current instruction address. |
| // In short, the value of the field can be shifted left or right, then added |
| // to, or subtracted from, the instruction address. |
| |
| FormatInfo* format_info = new FormatInfo(); |
| |
| std::string::size_type pos = 0; |
| pos = skip_space(expr, pos); |
| if (pos == std::string::npos) { |
| delete format_info; |
| return absl::InternalError("Empty format expression"); |
| } |
| |
| if (expr[pos] == '@') { |
| pos++; |
| format_info->use_address = true; |
| pos = skip_space(expr, pos); |
| if (pos == std::string::npos) { |
| return format_info; |
| } |
| |
| if (expr[pos] == '-') { |
| format_info->operation = "-"; |
| } else if (expr[pos] == '+') { |
| format_info->operation = "+"; |
| } else { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("@ must be followed by a '+' or a '-' in '", expr, "'")); |
| } |
| pos++; |
| } |
| |
| pos = skip_space(expr, pos); |
| if (pos == std::string::npos) { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("Malformed expression '", expr, "'")); |
| } |
| |
| if (expr[pos] != '(') { // No shift expression. |
| // Get the field identifier. |
| auto res = get_ident(expr, pos); |
| if (!res.ok()) { |
| delete format_info; |
| return res.status(); |
| } |
| auto [ident, new_pos] = res.value(); |
| if (!op->op_locator_map().contains(ident)) { |
| delete format_info; |
| return absl::InternalError(absl::StrCat("Invalid operand '", ident, |
| "' used in format for opcode'", |
| op->name(), "'")); |
| } |
| format_info->op_name = ident; |
| // Verify that there are no more characters in the expression. |
| pos = skip_space(expr, new_pos); |
| if (pos != std::string::npos) { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("Malformed expression '", expr, "'")); |
| } |
| } else { // expr[pos] == '(' |
| pos++; |
| pos = skip_space(expr, pos); |
| // The input expression has balanced parens, so we don't have to check for |
| // end of string. |
| |
| // Get the field identifier. |
| auto res = get_ident(expr, pos); |
| if (!res.ok()) { |
| delete format_info; |
| return res.status(); |
| } |
| auto [ident, new_pos] = res.value(); |
| format_info->op_name = ident; |
| |
| pos = skip_space(expr, new_pos); |
| |
| // Get the shift direction. |
| if (expr.substr(pos, 2) == "<<") { |
| format_info->do_left_shift = true; |
| } else if (expr.substr(pos, 2) != ">>") { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("Missing shift in expression '", expr, "'")); |
| } |
| pos += 2; |
| |
| // Get the shift amount. |
| pos = skip_space(expr, pos); |
| |
| std::string num; |
| while (std::isdigit(expr[pos])) { |
| num.push_back(expr[pos]); |
| pos++; |
| } |
| if (num.empty()) { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("Malformed expression - no shift amount '", expr, "'")); |
| } |
| format_info->shift_amount = std::stoi(num); |
| |
| // Verify close paren, and that there aren't any other characters after |
| // that. |
| pos = skip_space(expr, pos); |
| if (expr[pos] != ')') { |
| delete format_info; |
| return absl::InternalError( |
| absl::StrCat("Malformed expression - expected ')' '", expr, "'")); |
| } |
| pos++; |
| pos = skip_space(expr, pos); |
| if (pos != std::string::npos) { |
| delete format_info; |
| return absl::InternalError(absl::StrCat( |
| "Malformed expression - extra characters after ')' '", expr, "'")); |
| } |
| } |
| return format_info; |
| } |
| |
| absl::StatusOr<std::string> InstructionSetVisitor::ParseNumberFormat( |
| std::string format) { |
| std::string::size_type pos = 0; |
| std::string format_string = "%"; |
| bool leading_zero = false; |
| if (format[pos] == '0') { |
| leading_zero = true; |
| format_string.push_back('0'); |
| pos++; |
| } |
| // If there's a leading zero, there has to be a width. Signal error |
| // otherwise. |
| if (leading_zero && !std::isdigit(format[pos])) { |
| return absl::InternalError( |
| absl::StrCat("Format width required when a leading 0 is specified - '", |
| format.substr(0, pos + 1), "'")); |
| } |
| // Read the format width. It's an error if it's three digits, otherwise, |
| // just roll with it. |
| if (std::isdigit(format[pos])) { |
| std::string number = format.substr(pos++, 1); |
| if (std::isdigit(format[pos])) { |
| number.push_back(format[pos++]); |
| if (std::isdigit(format[pos])) { |
| return absl::InternalError( |
| absl::StrCat("Format width > than 3 digits not allowed '", |
| format.substr(0, pos + 1), "'")); |
| } |
| } |
| format_string.append(number); |
| } |
| // Read the number base. |
| if ((format[pos] != 'o') && (format[pos] != 'd') && (format[pos] != 'x') && |
| (format[pos] != 'X')) { |
| return absl::InternalError( |
| absl::StrCat("Illegal format specifier '", std::string(1, format[pos]), |
| "' in '", format.substr(0, pos + 1), "'")); |
| } |
| format_string.push_back(format[pos++]); |
| if (pos < format.size()) { |
| return absl::InternalError( |
| absl::StrCat("Too many characters in format specifier '", format, "'")); |
| } |
| return format_string; |
| } |
| |
| // The following methods are used to generate the prologs and epilogs in the |
| // emitted files. |
| std::string InstructionSetVisitor::GenerateHdrFileProlog( |
| absl::string_view file_name, absl::string_view opcode_file_name, |
| absl::string_view guard_name, absl::string_view encoding_base_name, |
| const std::vector<std::string>& namespaces) { |
| std::string output; |
| absl::StrAppend(&output, "#ifndef ", guard_name, |
| "\n" |
| "#define ", |
| guard_name, |
| "\n" |
| "\n" |
| "#include <functional>\n" |
| "#include <map>\n" |
| "#include <vector>\n" |
| "\n" |
| "#include \"mpact/sim/generic/arch_state.h\"\n" |
| "#include " |
| "\"mpact/sim/generic/instruction.h\"\n" |
| "#include \"", |
| opcode_file_name, |
| "\"\n" |
| "\n"); |
| |
| for (const auto& namespace_name : namespaces) { |
| absl::StrAppend(&output, "namespace ", namespace_name, " {\n"); |
| } |
| absl::StrAppend( |
| &output, |
| "\n" |
| "using ::mpact::sim::generic::Instruction;\n" |
| "using SemFunc = " |
| "::mpact::sim::generic::Instruction::SemanticFunction;\n" |
| "using ::mpact::sim::generic::ArchState;\n" |
| "using ::mpact::sim::generic::PredicateOperandInterface;\n" |
| "using ::mpact::sim::generic::SourceOperandInterface;\n" |
| "using ::mpact::sim::generic::DestinationOperandInterface;\n" |
| "using ::mpact::sim::generic::ResourceOperandInterface;\n" |
| "using SimpleResourceVector = std::vector<SimpleResourceEnum>;\n" |
| "\n"); |
| // Emit encoding base class. |
| absl::StrAppend(&output, "class ", encoding_base_name, " {\n public:\n"); |
| absl::StrAppend(&output, " virtual ~", encoding_base_name, |
| "() = default;\n\n"); |
| // Get opcode method. |
| absl::StrAppend( |
| &output, |
| " virtual OpcodeEnum GetOpcode(SlotEnum slot, int entry) = 0;\n"); |
| std::string optional_instruction; |
| if (generator_version_ == 2) { |
| optional_instruction = "Instruction *inst, "; |
| } |
| // Get resource methods. |
| absl::StrAppend( |
| &output, " virtual ResourceOperandInterface *GetSimpleResourceOperand", |
| "(", optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, SimpleResourceVector " |
| "&resource_vec, int end) { return nullptr;}\n"); |
| absl::StrAppend( |
| &output, |
| " virtual ResourceOperandInterface * " |
| "GetComplexResourceOperand", |
| "(", optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum " |
| "resource_op, int begin, int end) { return nullptr; }\n"); |
| absl::StrAppend( |
| &output, |
| " virtual std::vector<ResourceOperandInterface *> " |
| "GetComplexResourceOperands", |
| "(", optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum " |
| "resource_op, int begin, int end) { return {}; }\n"); |
| // For each operand type, declare the pure virtual method that returns the |
| // given operand. |
| absl::StrAppend(&output, |
| " virtual PredicateOperandInterface *GetPredicate" |
| "(", |
| optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, PredOpEnum " |
| "pred_op) { return nullptr; }\n"); |
| absl::StrAppend(&output, |
| " virtual SourceOperandInterface *GetSource" |
| "(", |
| optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, SourceOpEnum " |
| "source_op, int source_no) { return nullptr;}\n"); |
| absl::StrAppend( |
| &output, |
| " virtual std::vector<SourceOperandInterface *> GetSources" |
| "(", |
| optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, ListSourceOpEnum " |
| "list_source_op, int source_no) { return {};}\n"); |
| absl::StrAppend(&output, |
| " virtual DestinationOperandInterface *GetDestination" |
| "(", |
| optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, " |
| "DestOpEnum list_dest_op, int dest_no, int latency)" |
| " { return nullptr; }\n"); |
| absl::StrAppend( |
| &output, |
| " virtual std::vector<DestinationOperandInterface *> GetDestinations" |
| "(", |
| optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum opcode, " |
| "ListDestOpEnum dest_op, int dest_no, const std::vector<int> &latency)" |
| " { return {}; };\n"); |
| // Destination operand latency getter for destination operands with '*' |
| // as latency. |
| absl::StrAppend( |
| &output, " virtual int GetLatency(", optional_instruction, |
| "SlotEnum slot, int entry, OpcodeEnum " |
| "opcode, DestOpEnum dest_op, int dest_no) { return 0; };\n", |
| " virtual std::vector<int> GetLatency(SlotEnum slot, int entry, " |
| "OpcodeEnum " |
| "opcode, ListDestOpEnum dest_op, int dest_no) { return {0}; }\n"); |
| |
| absl::StrAppend(&output, "};\n\n"); |
| absl::StrAppend(&output, |
| "using OperandSetter = " |
| "std::vector<void (*)(Instruction *, ", |
| encoding_base_name, |
| "*, OpcodeEnum, SlotEnum, int)>;\n" |
| "using DisassemblySetter = void(*)(Instruction *);\n" |
| "using ResourceSetter = void(*)(Instruction *, ", |
| encoding_base_name, |
| "*, SlotEnum, int);\n" |
| "using SemFuncSetter = std::vector<SemFunc>;\n" |
| "using AttributeSetter = void(*)(Instruction *);\n" |
| "struct InstructionInfo {\n" |
| " OperandSetter operand_setter;\n" |
| " DisassemblySetter disassembly_setter;\n" |
| " ResourceSetter resource_setter;\n" |
| " AttributeSetter attribute_setter;\n" |
| " SemFuncSetter semfunc;\n" |
| " int instruction_size;\n" |
| "};\n" |
| "\n"); |
| return output; |
| } |
| |
| std::tuple<std::string, std::string> |
| InstructionSetVisitor::GenerateEncFilePrologs( |
| absl::string_view file_name, absl::string_view guard_name, |
| absl::string_view opcode_file_name, absl::string_view encoding_type_name, |
| const std::vector<std::string>& namespaces) { |
| std::string h_output; |
| std::string cc_output; |
| absl::StrAppend(&h_output, "#ifndef ", guard_name, |
| "\n" |
| "#define ", |
| guard_name, |
| "\n" |
| "\n" |
| "#include <array>\n" |
| "#include <string>\n" |
| "#include <vector>\n" |
| "\n" |
| "#include \"absl/container/flat_hash_map.h\"\n" |
| "#include \"absl/status/status.h\"\n" |
| "#include \"absl/status/statusor.h\"\n" |
| "#include \"absl/strings/string_view.h\"\n" |
| "#include " |
| "\"mpact/sim/util/asm/opcode_assembler_interface.h\"\n" |
| "#include \"mpact/sim/util/asm/resolver_interface.h\"\n" |
| "#include \"re2/re2.h\"\n" |
| "#include \"re2/set.h\"\n" |
| "#include \"", |
| opcode_file_name, |
| "\"\n" |
| "\n"); |
| absl::StrAppend(&cc_output, "#include \"", file_name, |
| "\"\n" |
| "\n" |
| "#include <array>\n" |
| "#include <string>\n" |
| "#include <vector>\n" |
| "\n" |
| "#include \"absl/status/status.h\"\n" |
| "#include \"absl/status/statusor.h\"\n" |
| "#include \"absl/strings/str_cat.h\"\n" |
| "#include \"absl/strings/string_view.h\"\n" |
| "#include " |
| "\"mpact/sim/util/asm/opcode_assembler_interface.h\"\n" |
| "#include \"mpact/sim/util/asm/resolver_interface.h\"\n" |
| "#include \"re2/re2.h\"\n" |
| "#include \"re2/set.h\"\n" |
| "#include \"", |
| opcode_file_name, |
| "\"\n" |
| "\n"); |
| |
| for (const auto& namespace_name : namespaces) { |
| absl::StrAppend(&h_output, "namespace ", namespace_name, " {\n"); |
| absl::StrAppend(&cc_output, "namespace ", namespace_name, " {\n"); |
| } |
| absl::StrAppend(&h_output, "\n"); |
| absl::StrAppend(&cc_output, "\n"); |
| return {h_output, cc_output}; |
| } |
| |
| std::string InstructionSetVisitor::GenerateHdrFileEpilog( |
| absl::string_view guard_name, const std::vector<std::string>& namespaces) { |
| std::string output; |
| absl::StrAppend(&output, GenerateNamespaceEpilog(namespaces)); |
| absl::StrAppend(&output, "\n#endif // ", guard_name, "\n"); |
| return output; |
| } |
| |
| std::string InstructionSetVisitor::GenerateCcFileProlog( |
| absl::string_view hdr_file_name, bool use_includes, |
| const std::vector<std::string>& namespaces) { |
| std::string output; |
| // Include files. |
| absl::StrAppend(&output, "#include \"", hdr_file_name, "\"\n"); |
| absl::StrAppend(&output, |
| "\n#include <array>\n\n" |
| "#include \"absl/strings/str_format.h\"\n\n"); |
| if (use_includes) { |
| for (auto& include_file : include_files_) { |
| absl::StrAppend(&output, "#include ", include_file, "\n"); |
| } |
| } |
| absl::StrAppend(&output, "\n"); |
| // Namespaces. |
| for (const auto& namespace_name : namespaces) { |
| absl::StrAppend(&output, "namespace ", namespace_name, " {\n"); |
| } |
| absl::StrAppend(&output, "\n"); |
| return output; |
| } |
| |
| std::string InstructionSetVisitor::GenerateSimpleHdrProlog( |
| absl::string_view guard_name, const std::vector<std::string>& namespaces) { |
| std::string output; |
| absl::StrAppend(&output, "#ifndef ", guard_name, "\n#define ", guard_name, |
| "\n\n"); |
| |
| for (const auto& namespace_name : namespaces) { |
| absl::StrAppend(&output, "namespace ", namespace_name, " {\n"); |
| } |
| absl::StrAppend(&output, "\n"); |
| return output; |
| } |
| |
| std::string InstructionSetVisitor::GenerateNamespaceEpilog( |
| const std::vector<std::string>& namespaces) { |
| std::string output; |
| // Close up namespaces. |
| absl::StrAppend(&output, "\n"); |
| for (auto namespace_name = namespaces.crbegin(); |
| namespace_name != namespaces.crend(); ++namespace_name) { |
| absl::StrAppend(&output, "} // namespace ", *namespace_name, "\n"); |
| } |
| return output; |
| } |
| |
| absl::Status InstructionSetVisitor::AddConstant(absl::string_view name, |
| absl::string_view, |
| TemplateExpression* expr) { |
| if (constant_map_.contains(name)) { |
| return absl::AlreadyExistsError( |
| absl::StrCat("Constant redefinition of '", name, "'")); |
| } |
| constant_map_.emplace(name, expr); |
| return absl::OkStatus(); |
| } |
| |
| TemplateExpression* InstructionSetVisitor::GetConstExpression( |
| absl::string_view name) const { |
| auto iter = constant_map_.find(name); |
| if (iter == constant_map_.end()) return nullptr; |
| return iter->second; |
| } |
| |
| } // namespace instruction_set |
| } // namespace machine_description |
| } // namespace sim |
| } // namespace mpact |