| // 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/bin_format_visitor.h" |
| |
| #include <deque> |
| #include <fstream> |
| #include <iostream> |
| #include <istream> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_split.h" |
| #include "mpact/sim/decoder/bin_decoder.h" |
| #include "mpact/sim/decoder/bin_encoding_info.h" |
| #include "mpact/sim/decoder/bin_format_contexts.h" |
| #include "mpact/sim/decoder/format.h" |
| #include "mpact/sim/decoder/format_name.h" |
| #include "mpact/sim/decoder/instruction_encoding.h" |
| #include "mpact/sim/decoder/instruction_group.h" |
| #include "mpact/sim/decoder/overlay.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace decoder { |
| namespace bin_format { |
| |
| constexpr char kTemplatedExtractBits[] = R"foo( |
| namespace internal { |
| |
| template <typename T> |
| static inline T ExtractBits(const uint8_t *data, int data_size, |
| int bit_index, int width) { |
| if (width == 0) return 0; |
| |
| int byte_pos = bit_index >> 3; |
| int end_byte = (bit_index + width - 1) >> 3; |
| int start_bit = bit_index & 0x7; |
| |
| // If it is only from one byte, extract and return. |
| if (byte_pos == end_byte) { |
| uint8_t mask = 0xff >> start_bit; |
| return (mask & data[byte_pos]) >> (8 - start_bit - width); |
| } |
| |
| // Extract from the first byte. |
| T val = 0; |
| val = data[byte_pos++] & 0xff >> start_bit; |
| int remainder = width - (8 - start_bit); |
| while (remainder >= 8) { |
| val = (val << 8) | data[byte_pos++]; |
| remainder -= 8; |
| } |
| |
| // Extract any remaining bits. |
| if (remainder > 0) { |
| val <<= remainder; |
| int shift = 8 - remainder; |
| uint8_t mask = 0b1111'1111 << shift; |
| val |= (data[byte_pos] & mask) >> shift; |
| } |
| return val; |
| } |
| |
| } // namespace internal |
| |
| )foo"; |
| |
| BinFormatVisitor::~BinFormatVisitor() { |
| for (auto *wrapper : antlr_parser_wrappers_) { |
| delete wrapper; |
| } |
| antlr_parser_wrappers_.clear(); |
| } |
| |
| using ::mpact::sim::machine_description::instruction_set::ToHeaderGuard; |
| |
| absl::Status BinFormatVisitor::Process( |
| const std::string &file_name, const std::string &decoder_name, |
| absl::string_view prefix, const std::vector<std::string> &include_roots, |
| absl::string_view directory) { |
| decoder_name_ = decoder_name; |
| |
| include_dir_vec_.push_back("."); |
| for (const auto &root : include_roots) { |
| include_dir_vec_.push_back(root); |
| } |
| |
| std::istream *source_stream = &std::cin; |
| |
| if (!file_name.empty()) { |
| source_stream = new std::fstream(file_name, std::fstream::in); |
| } |
| // Create an antlr4 stream from the input stream. |
| BinFmtAntlrParserWrapper parser_wrapper(source_stream); |
| |
| // Create and add the error listener. |
| set_error_listener(std::make_unique<decoder::DecoderErrorListener>()); |
| error_listener_->set_file_name(file_name); |
| 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(); |
| (void)top_level; |
| if (!file_name.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. |
| auto encoding_info = VisitTopLevel(top_level, decoder_name); |
| // Include files may generate additional syntax errors. |
| if (encoding_info == nullptr) { |
| LOG(ERROR) << "No encoding specified"; |
| return absl::InternalError("No encoding specified"); |
| } |
| if (error_listener_->HasError() > 0) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| |
| PerformEncodingChecks(encoding_info.get()); |
| // Fail if there are errors. |
| if (error_listener_->HasError() > 0) { |
| return absl::InternalError("Errors encountered - terminating."); |
| } |
| |
| // Create output streams for .h and .cc files. |
| std::string dot_h_name = absl::StrCat(prefix, "_bin_decoder.h"); |
| std::string dot_cc_name = absl::StrCat(prefix, "_bin_decoder.cc"); |
| std::ofstream dot_h_file(absl::StrCat(directory, "/", dot_h_name)); |
| std::ofstream dot_cc_file(absl::StrCat(directory, "/", dot_cc_name)); |
| |
| auto [h_output, cc_output] = EmitFilePrefix(dot_h_name, encoding_info.get()); |
| dot_h_file << h_output; |
| dot_cc_file << cc_output; |
| // Output file prefix is the input file name. |
| auto [h_output2, cc_output2] = EmitCode(encoding_info.get()); |
| dot_h_file << h_output2; |
| dot_cc_file << cc_output2; |
| auto [h_output3, cc_output3] = |
| EmitFileSuffix(dot_h_name, encoding_info.get()); |
| dot_h_file << h_output3; |
| dot_cc_file << cc_output3; |
| dot_h_file.close(); |
| dot_cc_file.close(); |
| return absl::OkStatus(); |
| } |
| |
| void BinFormatVisitor::PerformEncodingChecks(BinEncodingInfo *encoding) { |
| encoding->decoder()->CheckEncodings(); |
| } |
| |
| BinFormatVisitor::StringPair BinFormatVisitor::EmitFilePrefix( |
| const std::string &dot_h_name, BinEncodingInfo *encoding_info) { |
| std::string h_string; |
| std::string cc_string; |
| |
| std::string guard_name = ToHeaderGuard(dot_h_name); |
| absl::StrAppend(&h_string, "#ifndef ", guard_name, |
| "\n" |
| "#define ", |
| guard_name, |
| "\n" |
| "\n" |
| "#include <iostream>\n" |
| "#include <cstdint>\n" |
| "\n" |
| "#include \"absl/functional/any_invocable.h\"\n" |
| "\n\n"); |
| for (auto const &include_file : encoding_info->include_files()) { |
| absl::StrAppend(&h_string, "#include ", include_file, "\n"); |
| } |
| absl::StrAppend(&h_string, "\n"); |
| absl::StrAppend(&cc_string, "#include \"", dot_h_name, "\"\n\n"); |
| for (auto &name_space : encoding_info->decoder()->namespaces()) { |
| auto name_space_str = absl::StrCat("namespace ", name_space, " {\n"); |
| absl::StrAppend(&h_string, name_space_str); |
| absl::StrAppend(&cc_string, name_space_str); |
| } |
| absl::StrAppend(&h_string, "\n"); |
| absl::StrAppend(&cc_string, "\n"); |
| // Write out the templated extractor function used by the other methods. |
| absl::StrAppend(&h_string, kTemplatedExtractBits); |
| return {h_string, cc_string}; |
| } |
| |
| BinFormatVisitor::StringPair BinFormatVisitor::EmitFileSuffix( |
| const std::string &dot_h_name, BinEncodingInfo *encoding_info) { |
| std::string h_string; |
| std::string cc_string; |
| |
| absl::StrAppend(&h_string, "\n"); |
| absl::StrAppend(&cc_string, "\n"); |
| auto &namespaces = encoding_info->decoder()->namespaces(); |
| for (auto rptr = namespaces.rbegin(); rptr != namespaces.rend(); rptr++) { |
| std::string name_space = absl::StrCat("} // namespace ", *rptr, "\n"); |
| absl::StrAppend(&h_string, name_space); |
| absl::StrAppend(&cc_string, name_space); |
| } |
| std::string guard_name = ToHeaderGuard(dot_h_name); |
| absl::StrAppend(&h_string, "\n#endif // ", guard_name); |
| return {h_string, cc_string}; |
| } |
| |
| BinFormatVisitor::StringPair BinFormatVisitor::EmitCode( |
| BinEncodingInfo *encoding) { |
| std::string h_string; |
| std::string cc_string; |
| std::string group_string; |
| // Write out the inline functions for bitfield and overlay extractions. |
| for (auto &[unused, format_ptr] : encoding->format_map()) { |
| absl::StrAppend(&h_string, format_ptr->GenerateExtractors()); |
| } |
| absl::flat_hash_set<std::string> groups; |
| auto *decoder = encoding->decoder(); |
| // Generate the code for decoders. |
| for (auto *group : decoder->instruction_group_vec()) { |
| auto [h_decoder, cc_decoder] = group->EmitCode(); |
| absl::StrAppend(&h_string, h_decoder); |
| absl::StrAppend(&cc_string, cc_decoder); |
| // Write out some summary information about the instruction encodings. |
| // Useful for now to ensure that the info is correct. |
| absl::StrAppend(&group_string, group->WriteGroup()); |
| } |
| absl::StrAppend(&h_string, group_string); |
| return {h_string, cc_string}; |
| } |
| |
| // Parse the range and convert to a BitRange. |
| BitRange BinFormatVisitor::GetBitIndexRange(BitIndexRangeCtx *ctx) { |
| int start = ConvertToInt(ctx->number(0)); |
| int stop = start; |
| if (ctx->number().size() == 2) { |
| stop = ConvertToInt(ctx->number(1)); |
| } |
| return BitRange{start, stop}; |
| } |
| |
| // Parse a binary number string such as 0b1010'0111 and return a BinaryNum |
| // to encode the value and width. |
| BinaryNum BinFormatVisitor::ParseBinaryNum(TerminalNode *node) { |
| std::string bin_str = node->getText(); |
| if (bin_str.substr(0, 2) != "0b") { |
| error_listener_->semanticError(node->getSymbol(), |
| "Illegal binary number string"); |
| return BinaryNum{/*value=*/0, /*width=*/-1}; |
| } |
| auto digits = bin_str.substr(2); |
| int64_t value = 0; |
| int width = 0; |
| for (auto d : digits) { |
| if (d == '\'') continue; |
| if ((d != '1') && (d != '0')) { |
| error_listener_->semanticError(node->getSymbol(), |
| "Illegal binary number string"); |
| return BinaryNum{/*value=*/0, /*width=*/-1}; |
| } |
| value <<= 1; |
| value |= d - '0'; |
| width++; |
| } |
| return BinaryNum{value, width}; |
| } |
| |
| // Parse a number string and return the value. |
| int BinFormatVisitor::ConvertToInt(NumberCtx *ctx) { |
| // Binary has to be handled separately. |
| auto bin_number = ctx->BIN_NUMBER(); |
| if (bin_number != nullptr) { |
| return static_cast<int>(ParseBinaryNum(bin_number).value); |
| } |
| // Hex, octal and decimal. |
| int ret_val = std::stoi(ctx->getText(), nullptr, 0); |
| return ret_val; |
| } |
| |
| std::unique_ptr<BinEncodingInfo> BinFormatVisitor::VisitTopLevel( |
| TopLevelCtx *ctx, const std::string &decoder_name) { |
| PreProcessDeclarations(ctx->declaration_list()); |
| // At this point we have all the formats, instruction groups and decoders. |
| // First make sure that the named decoder exists. |
| auto decoder_iter = decoder_decl_map_.find(decoder_name); |
| if (decoder_iter == decoder_decl_map_.end()) { |
| error_listener()->semanticError( |
| nullptr, absl::StrCat("No decoder '", decoder_name, "' declared")); |
| return nullptr; |
| } |
| // Visit the decoder. |
| auto bin_encoding_info = VisitDecoderDef(decoder_iter->second); |
| auto status = bin_encoding_info->CheckFormats(); |
| if (!status.ok()) { |
| error_listener_->semanticError(nullptr, status.message()); |
| return nullptr; |
| } |
| return bin_encoding_info; |
| } |
| |
| void BinFormatVisitor::PreProcessDeclarations(DeclarationListCtx *ctx) { |
| std::vector<IncludeFileCtx *> include_files; |
| |
| for (auto *declaration : ctx->declaration()) { |
| // Create map from format name to format ctx. |
| if (declaration->format_def() != nullptr) { |
| auto format_def = declaration->format_def(); |
| auto name = format_def->name->getText(); |
| auto iter = format_decl_map_.find(name); |
| if (iter != format_decl_map_.end()) { |
| error_listener()->semanticError( |
| format_def->start, absl::StrCat("Multiple definitions of format '", |
| name, "' first defined at line: ", |
| iter->second->start->getLine())); |
| continue; |
| } |
| format_decl_map_.emplace(name, format_def); |
| continue; |
| } |
| // Create map from instruction group name to instruction group ctx. |
| if (declaration->instruction_group_def() != nullptr) { |
| auto group_def = declaration->instruction_group_def(); |
| auto name = group_def->name->getText(); |
| auto iter = group_decl_map_.find(name); |
| if (iter != group_decl_map_.end()) { |
| error_listener()->semanticError( |
| group_def->start, |
| absl::StrCat( |
| "Multiple definitions of instruction group '", name, |
| "' first defined at line: ", iter->second->start->getLine())); |
| continue; |
| } |
| group_decl_map_.emplace(name, group_def); |
| continue; |
| } |
| // Process bin_fmt include files. |
| include_files.push_back(declaration->include_file()); |
| } |
| // Create map from decoder name to decoder ctx. |
| for (auto *decoder_def : ctx->decoder_def()) { |
| auto name = decoder_def->name->getText(); |
| auto iter = decoder_decl_map_.find(name); |
| if (iter != decoder_decl_map_.end()) { |
| error_listener()->semanticError( |
| decoder_def->start, absl::StrCat("Multiple definitions of decoder '", |
| name, "' first defined at line: ", |
| iter->second->start->getLine())); |
| continue; |
| } |
| decoder_decl_map_.emplace(name, decoder_def); |
| } |
| for (auto *include_file_ctx : include_files) { |
| VisitIncludeFile(include_file_ctx); |
| } |
| } |
| |
| void BinFormatVisitor::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; |
| } |
| } |
| std::fstream include_file; |
| // Open include file. |
| for (auto const &dir : include_dir_vec_) { |
| std::string include_name = absl::StrCat(dir, "/", file_name); |
| include_file.open(include_name, std::fstream::in); |
| if (include_file.is_open()) break; |
| } |
| if (!include_file.is_open()) { |
| // Try a local file. |
| include_file.open(file_name, std::fstream::in); |
| if (!include_file.is_open()) { |
| error_listener()->semanticError( |
| ctx->start, absl::StrCat("Failed to open '", file_name, "'")); |
| return; |
| } |
| } |
| std::string previous_file_name = error_listener()->file_name(); |
| error_listener()->set_file_name(file_name); |
| auto *include_parser = new BinFmtAntlrParserWrapper(&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()); |
| DeclarationListCtx *declaration_list = |
| include_parser->parser()->declaration_list_w_eof()->declaration_list(); |
| include_file.close(); |
| error_listener()->set_file_name(previous_file_name); |
| if (error_listener()->syntax_error_count() > 0) { |
| return; |
| } |
| include_file_stack_.push_back(file_name); |
| PreProcessDeclarations(declaration_list); |
| include_file_stack_.pop_back(); |
| } |
| |
| void BinFormatVisitor::VisitFormatDef(FormatDefCtx *ctx, |
| BinEncodingInfo *encoding_info) { |
| if (ctx == nullptr) return; |
| // Get the format name and width. |
| std::string format_name = ctx->IDENT()->getText(); |
| // If we have already visited the format, just return. There is no error. |
| if (encoding_info->GetFormat(format_name) != nullptr) return; |
| int format_width = -1; |
| if (ctx->width != nullptr) { |
| format_width = ConvertToInt(ctx->width->number()); |
| } else if (ctx->inherits_from() == nullptr) { |
| // Must specify either a width or it must inherit from a format that has |
| // a width. |
| error_listener_->semanticError( |
| ctx->inherits_from()->start, |
| absl::StrCat("Format '", format_name, |
| "': must specify a width or inherited format")); |
| return; |
| } |
| absl::StatusOr<Format *> format_res; |
| if (ctx->inherits_from() != nullptr) { |
| std::string parent_name = ctx->inherits_from()->IDENT()->getText(); |
| auto *parent_format = encoding_info->GetFormat(parent_name); |
| if (parent_format == nullptr) { |
| auto iter = format_decl_map_.find(parent_name); |
| if (iter != format_decl_map_.end()) { |
| VisitFormatDef(iter->second, encoding_info); |
| } |
| parent_format = encoding_info->GetFormat(parent_name); |
| } |
| if (parent_format == nullptr) { |
| error_listener_->semanticError( |
| ctx->inherits_from()->start, |
| absl::StrCat("Parent format '", parent_name, "' not defined")); |
| return; |
| } else { |
| int parent_width = parent_format->declared_width(); |
| if ((format_width != -1) && (format_width != parent_width)) { |
| error_listener_->semanticError( |
| ctx->inherits_from()->start, |
| absl::StrCat("Format '", format_name, "': declared width (", |
| format_width, ") differs from inherited width (", |
| parent_width, ")")); |
| return; |
| } |
| } |
| format_res = |
| encoding_info->AddFormat(format_name, format_width, parent_name); |
| } else { |
| format_res = encoding_info->AddFormat(format_name, format_width); |
| } |
| if (!format_res.ok()) { |
| error_listener_->semanticError(ctx->start, format_res.status().message()); |
| return; |
| } |
| // Parse the fields in the format. |
| auto format = format_res.value(); |
| for (auto field : ctx->format_field_defs()->field_def()) { |
| VisitFieldDef(field, format); |
| } |
| // Parse the overlays in the format. |
| for (auto overlay : ctx->format_field_defs()->overlay_def()) { |
| VisitOverlayDef(overlay, format); |
| } |
| auto status = format->ComputeAndCheckFormatWidth(); |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->start, status.message()); |
| } |
| } |
| |
| void BinFormatVisitor::VisitFieldDef(FieldDefCtx *ctx, Format *format) { |
| if (ctx == nullptr) return; |
| |
| std::string name(ctx->IDENT()->getText()); |
| if (ctx->FORMAT() == nullptr) { |
| // If it's a field definition, add the field. |
| bool is_signed = ctx->sign_spec()->SIGNED() != nullptr; |
| int width = ConvertToInt(ctx->index()->number()); |
| // For now, only support fields <= 64 bit wide. |
| if (width > 64) { |
| error_listener_->semanticError( |
| ctx->width->number()->start, |
| "Only fields <= 64 bits are supported for now."); |
| return; |
| } |
| auto status = format->AddField(name, is_signed, width); |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->IDENT()->getSymbol(), |
| status.message()); |
| } |
| } else { |
| // Otherwise it is a reference to a format (which may be defined later |
| // in the file). Add it and adjust the width later. |
| int size = 1; |
| if (ctx->index() != nullptr) { |
| size = ConvertToInt(ctx->index()->number()); |
| } |
| format->AddFormatReferenceField(name, size, ctx->start); |
| } |
| } |
| |
| void BinFormatVisitor::VisitOverlayDef(OverlayDefCtx *ctx, Format *format) { |
| if (ctx == nullptr) return; |
| |
| std::string name(ctx->IDENT()->getText()); |
| bool is_signed = ctx->sign_spec()->SIGNED() != nullptr; |
| int width = ConvertToInt(ctx->width->number()); |
| // For now, only support overlays <= 64 bit wide. |
| if (width > 64) { |
| error_listener_->semanticError( |
| ctx->width->number()->start, |
| "Only overlays <= 64 bits are supported for now."); |
| return; |
| } |
| // Visit the bitfield spec items. |
| auto overlay_res = format->AddFieldOverlay(name, is_signed, width); |
| if (!overlay_res.ok()) { |
| error_listener_->semanticError(ctx->start, overlay_res.status().message()); |
| } |
| auto *overlay = overlay_res.value(); |
| for (auto *bit_field : ctx->bit_field_list()->bit_field_spec()) { |
| VisitOverlayBitField(bit_field, overlay); |
| } |
| if (overlay->computed_width() != overlay->declared_width()) { |
| error_listener_->semanticError( |
| ctx->start, absl::StrCat("Declared width (", overlay->declared_width(), |
| ") differs from computed width (", |
| overlay->computed_width(), ")")); |
| } |
| } |
| |
| void BinFormatVisitor::VisitOverlayBitField(BitFieldCtx *ctx, |
| Overlay *overlay) { |
| if (ctx == nullptr) return; |
| |
| if (ctx->IDENT() != nullptr) { |
| // This is a reference to a bit field in the format. |
| if (ctx->bit_range_list() != nullptr) { |
| std::vector<BitRange> bit_ranges_; |
| for (auto *range : ctx->bit_range_list()->bit_index_range()) { |
| bit_ranges_.push_back(GetBitIndexRange(range)); |
| } |
| auto status = overlay->AddFieldReference(ctx->IDENT()->getText(), |
| std::move(bit_ranges_)); |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->start, status.message()); |
| } |
| return; |
| } |
| auto status = overlay->AddFieldReference(ctx->IDENT()->getText()); |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->start, status.message()); |
| } |
| return; |
| } |
| // Is this a reference to the format itself? |
| if (ctx->bit_range_list() != nullptr) { |
| std::vector<BitRange> bit_ranges_; |
| for (auto *range : ctx->bit_range_list()->bit_index_range()) { |
| bit_ranges_.push_back(GetBitIndexRange(range)); |
| } |
| auto status = overlay->AddFormatReference(std::move(bit_ranges_)); |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->start, status.message()); |
| } |
| return; |
| } |
| |
| // This must be a binary number string. |
| auto bin_num = ParseBinaryNum(ctx->bin_number()->BIN_NUMBER()); |
| overlay->AddBitConstant(bin_num); |
| } |
| |
| InstructionGroup *BinFormatVisitor::VisitInstructionGroupDef( |
| InstructionGroupDefCtx *ctx, BinEncodingInfo *encoding_info) { |
| if (ctx == nullptr) return nullptr; |
| |
| // Create the named instruction group. |
| std::string group_name = ctx->name->getText(); |
| if (encoding_info->GetFormat(group_name) != nullptr) { |
| error_listener_->semanticError( |
| ctx->start, absl::StrCat(group_name, ": illegal use of format name")); |
| } |
| int width = ConvertToInt(ctx->number()); |
| std::string format_name = ctx->format->getText(); |
| auto iter = format_decl_map_.find(format_name); |
| if (iter == format_decl_map_.end()) { |
| error_listener_->semanticError( |
| ctx->start, absl::StrCat("No such format '", format_name, "'")); |
| return nullptr; |
| } |
| VisitFormatDef(iter->second, encoding_info); |
| auto inst_group_res = |
| encoding_info->AddInstructionGroup(group_name, width, format_name); |
| if (!inst_group_res.ok()) { |
| error_listener_->semanticError(ctx->start, |
| inst_group_res.status().message()); |
| return nullptr; |
| } |
| // Parse the instruction encoding definitions in the instruction group. |
| auto inst_group = inst_group_res.value(); |
| for (auto *inst_def : ctx->instruction_def()) { |
| VisitInstructionDef(inst_def, inst_group); |
| } |
| return inst_group_res.value(); |
| } |
| |
| void BinFormatVisitor::VisitInstructionDef(InstructionDefCtx *ctx, |
| InstructionGroup *inst_group) { |
| if (ctx == nullptr) return; |
| |
| // Get the instruction name and the format it refers to. |
| std::string name = ctx->name->getText(); |
| std::string format_name = ctx->format_name->getText(); |
| auto format = inst_group->encoding_info()->GetFormat(format_name); |
| if (format == nullptr) { |
| auto iter = format_decl_map_.find(format_name); |
| if (iter == format_decl_map_.end()) { |
| error_listener_->semanticError( |
| ctx->start, absl::StrCat("Format '", format_name, "' not found")); |
| return; |
| } |
| VisitFormatDef(iter->second, inst_group->encoding_info()); |
| format = inst_group->encoding_info()->GetFormat(format_name); |
| if (format == nullptr) return; |
| } |
| if (format->declared_width() != inst_group->width()) { |
| error_listener_->semanticError( |
| ctx->start, |
| absl::StrCat( |
| "Length of format '", format_name, "' (", format->declared_width(), |
| ") differs from the declared width of the instruction group (", |
| inst_group->width(), ")")); |
| return; |
| } |
| if (format->declared_width() > 64) { |
| error_listener_->semanticError( |
| ctx->start, "Instruction encoding cannot depend on format > 64bits"); |
| return; |
| } |
| auto *inst_encoding = inst_group->AddInstructionEncoding(name, format); |
| // Add constraints to the instruction encoding. |
| for (auto *constraint : ctx->field_constraint_list()->field_constraint()) { |
| VisitConstraint(constraint, inst_encoding); |
| } |
| } |
| |
| void BinFormatVisitor::VisitConstraint(FieldConstraintCtx *ctx, |
| InstructionEncoding *inst_encoding) { |
| if (ctx == nullptr) return; |
| |
| // Constraints are based on field names ==/!=/>/>=/</<= to a value. |
| std::string field_name = ctx->field_name->getText(); |
| std::string op = ctx->constraint_op()->getText(); |
| int value = ConvertToInt(ctx->number()); |
| absl::Status status; |
| if (op == "==") { |
| status = inst_encoding->AddEqualConstraint(field_name, value); |
| } else if (op == "!=") { |
| status = inst_encoding->AddOtherConstraint(ConstraintType::kNe, field_name, |
| value); |
| } else if (op == ">") { |
| status = inst_encoding->AddOtherConstraint(ConstraintType::kGt, field_name, |
| value); |
| } else if (op == ">=") { |
| status = inst_encoding->AddOtherConstraint(ConstraintType::kGe, field_name, |
| value); |
| } else if (op == "<") { |
| status = inst_encoding->AddOtherConstraint(ConstraintType::kLt, field_name, |
| value); |
| } else if (op == "<=") { |
| status = inst_encoding->AddOtherConstraint(ConstraintType::kLe, field_name, |
| value); |
| } |
| if (!status.ok()) { |
| error_listener_->semanticError(ctx->start, status.message()); |
| } |
| } |
| |
| std::unique_ptr<BinEncodingInfo> BinFormatVisitor::VisitDecoderDef( |
| DecoderDefCtx *ctx) { |
| if (ctx == nullptr) return nullptr; |
| std::string name = ctx->name->getText(); |
| |
| // First get the opcode enum. |
| int opcode_count = 0; |
| std::string opcode_enum; |
| for (auto *attr_ctx : ctx->decoder_attribute()) { |
| if (attr_ctx->opcode_enum_decl() != nullptr) { |
| auto opcode_enum_literal = |
| attr_ctx->opcode_enum_decl()->STRING_LITERAL()->getText(); |
| opcode_enum = |
| opcode_enum_literal.substr(1, opcode_enum_literal.size() - 2); |
| if (opcode_enum.empty()) { |
| error_listener_->semanticError(attr_ctx->start, |
| "Empty opcode enum string"); |
| } |
| if (opcode_count > 0) { |
| error_listener_->semanticError(attr_ctx->start, |
| "More than one opcode enum declaration"); |
| } |
| opcode_count++; |
| } |
| } |
| auto encoding_info = |
| std::make_unique<BinEncodingInfo>(opcode_enum, error_listener_.get()); |
| auto *decoder = encoding_info->AddBinDecoder(name); |
| if (decoder == nullptr) return nullptr; |
| absl::flat_hash_set<std::string> group_name_set; |
| int namespace_count = 0; |
| for (auto *attr_ctx : ctx->decoder_attribute()) { |
| // Include files. |
| if (attr_ctx->include_files() != nullptr) { |
| for (auto *include_file : attr_ctx->include_files()->include_file()) { |
| auto include_text = include_file->STRING_LITERAL()->getText(); |
| encoding_info->AddIncludeFile(include_text); |
| } |
| continue; |
| } |
| // Namespace declaration. |
| if (attr_ctx->namespace_decl() != nullptr) { |
| auto decl = attr_ctx->namespace_decl(); |
| for (auto *namespace_name : decl->namespace_ident()) { |
| decoder->namespaces().push_back(namespace_name->getText()); |
| } |
| if (namespace_count > 0) { |
| error_listener_->semanticError(attr_ctx->start, |
| "More than one namespace declaration"); |
| } |
| namespace_count++; |
| continue; |
| } |
| |
| // Instruction groups are listed as either a single instruction group, |
| // or a parent group that contains several individual groups. |
| |
| // If it is a single group process it here. |
| if ((attr_ctx->group_name() != nullptr) && |
| (attr_ctx->group_name()->group_name_list() == nullptr)) { |
| std::string group_name = attr_ctx->group_name()->IDENT()->getText(); |
| |
| auto map_iter = encoding_info->instruction_group_map().find(group_name); |
| InstructionGroup *inst_group = nullptr; |
| if (map_iter != encoding_info->instruction_group_map().end()) { |
| inst_group = map_iter->second; |
| } else { |
| auto iter = group_decl_map_.find(group_name); |
| if (iter != group_decl_map_.end()) { |
| inst_group = |
| VisitInstructionGroupDef(iter->second, encoding_info.get()); |
| } |
| if (inst_group == nullptr) { |
| error_listener_->semanticError( |
| attr_ctx->start, |
| absl::StrCat("No such instruction group: '", group_name, "'")); |
| continue; |
| } |
| } |
| if (group_name_set.contains(group_name)) { |
| error_listener_->semanticError( |
| attr_ctx->start, |
| absl::StrCat("Instruction group listed twice: ", group_name, "'")); |
| continue; |
| } |
| group_name_set.insert(group_name); |
| decoder->SelectInstructionGroupForDecoder(inst_group); |
| continue; |
| } |
| |
| // If it is a parent group, process it here. |
| if ((attr_ctx->group_name() != nullptr) && |
| (attr_ctx->group_name()->group_name_list() != nullptr)) { |
| // Check each of the child groups. Visit any that haven't been visited |
| // yet, and make sure they all specify the same format. |
| std::string group_name = attr_ctx->group_name()->IDENT()->getText(); |
| if (group_name_set.contains(group_name)) { |
| error_listener_->semanticError( |
| attr_ctx->start, |
| absl::StrCat("Instruction group listed twice: ", group_name, "'")); |
| continue; |
| } |
| std::vector<InstructionGroup *> child_groups; |
| std::string group_format_name; |
| // Iterate through the list of named "child" groups to combine. |
| for (auto *ident : attr_ctx->group_name()->group_name_list()->IDENT()) { |
| auto child_name = ident->getText(); |
| if (group_name_set.contains(child_name)) { |
| error_listener_->semanticError( |
| attr_ctx->start, absl::StrCat("Instruction group listed twice: ", |
| child_name, "'")); |
| continue; |
| } |
| auto map_iter = encoding_info->instruction_group_map().find(child_name); |
| InstructionGroup *child_group = nullptr; |
| if (map_iter != encoding_info->instruction_group_map().end()) { |
| child_group = map_iter->second; |
| } else { |
| auto iter = group_decl_map_.find(child_name); |
| if (iter != group_decl_map_.end()) { |
| child_group = |
| VisitInstructionGroupDef(iter->second, encoding_info.get()); |
| } |
| if (child_group == nullptr) { |
| error_listener_->semanticError( |
| attr_ctx->start, |
| absl::StrCat("No such instruction group found: '", child_name, |
| "'")); |
| continue; |
| } |
| } |
| if (child_groups.empty()) { |
| group_format_name = child_group->format_name(); |
| } |
| // Check that the child groups all use the same instruction format. |
| if (group_format_name != child_group->format_name()) { |
| error_listener_->semanticError( |
| attr_ctx->start, |
| absl::StrCat("Instruction group '", child_name, |
| "must use format '", group_format_name, |
| ", to be merged into group '", group_name, "'")); |
| continue; |
| } |
| child_groups.push_back(child_group); |
| } |
| if (child_groups.empty()) { |
| error_listener_->semanticError(attr_ctx->start, "No child groups"); |
| continue; |
| } |
| // Create the "parent" group and add all of the instructions from the |
| // child groups to it. |
| auto width = child_groups.front()->width(); |
| auto res = encoding_info->AddInstructionGroup(group_name, width, |
| group_format_name); |
| if (!res.ok()) { |
| error_listener_->semanticError(attr_ctx->start, res.status().message()); |
| continue; |
| } |
| auto parent_group = res.value(); |
| for (auto *child_group : child_groups) { |
| for (auto *encoding : child_group->encoding_vec()) { |
| parent_group->AddInstructionEncoding( |
| new InstructionEncoding(*encoding)); |
| } |
| } |
| group_name_set.insert(parent_group->name()); |
| decoder->SelectInstructionGroupForDecoder(parent_group); |
| continue; |
| } |
| } |
| if (group_name_set.empty()) { |
| error_listener_->semanticError(ctx->start, "No instruction groups found"); |
| } |
| return encoding_info; |
| } |
| |
| } // namespace bin_format |
| } // namespace decoder |
| } // namespace sim |
| } // namespace mpact |