| // 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_group.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "absl/container/btree_map.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "antlr4-runtime/Token.h" |
| #include "mpact/sim/decoder/bin_encoding_info.h" |
| #include "mpact/sim/decoder/decoder_error_listener.h" |
| #include "mpact/sim/decoder/encoding_group.h" |
| #include "mpact/sim/decoder/extract.h" |
| #include "mpact/sim/decoder/format_name.h" |
| #include "mpact/sim/decoder/instruction_encoding.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace decoder { |
| namespace bin_format { |
| |
| using ::mpact::sim::machine_description::instruction_set::ToPascalCase; |
| |
| InstructionGroup::InstructionGroup(std::string name, int width, |
| std::string format_name, |
| std::string opcode_enum, |
| BinEncodingInfo* encoding_info) |
| : name_(name), |
| width_(width), |
| format_name_(format_name), |
| opcode_enum_(opcode_enum), |
| encoding_info_(encoding_info) { |
| format_ = encoding_info->GetFormat(format_name); |
| } |
| |
| InstructionGroup::~InstructionGroup() { |
| for (auto* enc : encoding_vec_) { |
| delete enc; |
| } |
| encoding_map_.clear(); |
| encoding_vec_.clear(); |
| for (auto* group : encoding_group_vec_) { |
| delete group; |
| } |
| encoding_group_vec_.clear(); |
| } |
| |
| // Add an instruction encoding into the current group. Check that the format |
| // has the correct width, and that the format the encoding is defined in, or |
| // derives from the format associated with the instruction group. |
| InstructionEncoding* InstructionGroup::AddInstructionEncoding( |
| antlr4::Token* token, std::string name, Format* format, bool is_duplicate) { |
| if ((format != nullptr) && |
| ((format_ == nullptr) || (!format->IsDerivedFrom(format_)))) { |
| encoding_info_->error_listener()->semanticError( |
| token, absl::StrCat("Format '", format->name(), |
| "' used by instruction encoding '", name, |
| "' is not derived from '", format_name_, "'")); |
| return nullptr; |
| } |
| |
| // No need to double check width, since the format at this point derives |
| // from the the instruction group format. |
| |
| // Warn if the instruction name has been seen before. It might be fully valid |
| // that an instruction name has multiple encodings, but warn about it, in |
| // case it is an error. |
| if (encoding_name_map_.contains(name) && !is_duplicate) { |
| encoding_info_->error_listener()->semanticWarning( |
| token, absl::StrCat("Duplicate instruction opcode name '", name, |
| "' in group '", this->name(), "'.")); |
| } |
| auto* encoding = new InstructionEncoding(name, format, is_duplicate); |
| encoding_vec_.push_back(encoding); |
| encoding_name_map_.insert(std::make_pair(name, encoding)); |
| return encoding; |
| } |
| |
| void InstructionGroup::AddInstructionEncoding(InstructionEncoding* encoding) { |
| if (encoding_name_map_.contains(encoding->name()) && |
| !encoding->is_duplicate()) { |
| encoding_info_->error_listener()->semanticWarning( |
| nullptr, absl::StrCat("Duplicate instruction opcode name '", |
| encoding->name(), "' in group '", name(), "'.")); |
| } |
| encoding_name_map_.insert(std::make_pair(encoding->name(), encoding)); |
| encoding_vec_.push_back(encoding); |
| } |
| |
| void InstructionGroup::ProcessEncodings() { |
| if (encoding_vec_.empty()) { |
| encoding_info_->error_listener()->semanticWarning( |
| nullptr, |
| absl::StrCat("No encodings in instruction group: '", name(), "'")); |
| return; |
| } |
| // Insert the encodings into a map based on the mask value - grouping |
| // instructions with the same mask. |
| for (auto* enc : encoding_vec_) { |
| encoding_map_.insert(std::make_pair(enc->GetMask(), enc)); |
| } |
| encoding_group_vec_.push_back(new EncodingGroup(this, 0)); |
| for (auto& [unused, enc_ptr] : encoding_map_) { |
| bool is_added = false; |
| for (auto* group : encoding_group_vec_) { |
| if (group->CanAddEncoding(enc_ptr)) { |
| group->AddEncoding(enc_ptr); |
| is_added = true; |
| break; |
| } |
| } |
| if (!is_added) { |
| encoding_group_vec_.push_back(new EncodingGroup(this, 0)); |
| encoding_group_vec_.back()->AddEncoding(enc_ptr); |
| is_added = true; |
| } |
| } |
| for (auto* grp : encoding_group_vec_) { |
| grp->AddSubGroups(); |
| } |
| } |
| |
| // Check for encoding errors. |
| void InstructionGroup::CheckEncodings() { |
| for (auto* enc_grp : encoding_group_vec_) { |
| enc_grp->CheckEncodings(); |
| } |
| } |
| |
| absl::Status InstructionGroup::AddSpecialization( |
| const std::string& name, const std::string& parent_name, |
| InstructionEncoding* encoding) { |
| if (encoding_name_map_.contains(name)) { |
| encoding_info_->error_listener()->semanticError( |
| nullptr, |
| absl::StrCat("Duplicate instruction specialization opcode name '", name, |
| "' in group '", this->name(), "'.")); |
| return absl::AlreadyExistsError( |
| absl::StrCat("Duplicate instruction specialization opcode name '", name, |
| "' in group '", this->name(), "'.")); |
| } |
| encoding_name_map_.insert(std::make_pair(name, encoding)); |
| auto* parent_encoding = encoding_name_map_.at(parent_name); |
| return parent_encoding->AddSpecialization(name, encoding); |
| } |
| |
| // Helper function used to sort the instruction group elements in a vector. |
| static bool InstructionGroupLess(EncodingGroup* lhs, EncodingGroup* rhs) { |
| uint64_t lhs_value = 0; |
| uint64_t rhs_value = 0; |
| if (lhs->parent() == nullptr) { |
| auto grp_recipe = GetExtractionRecipe(lhs->constant()); |
| lhs_value = ExtractValue(lhs->encoding_vec()[0]->GetValue(), grp_recipe); |
| rhs_value = ExtractValue(rhs->encoding_vec()[0]->GetValue(), grp_recipe); |
| } else { |
| auto grp_recipe = GetExtractionRecipe(lhs->parent()->discriminator()); |
| lhs_value = ExtractValue(lhs->encoding_vec()[0]->GetValue(), grp_recipe); |
| rhs_value = ExtractValue(rhs->encoding_vec()[0]->GetValue(), grp_recipe); |
| } |
| return lhs_value < rhs_value; |
| } |
| |
| // Emit the code in the form of two strings that are returned in a tuple. |
| std::tuple<std::string, std::string> InstructionGroup::EmitDecoderCode() { |
| std::string h_string; |
| std::string cc_string; |
| |
| if (encoding_group_vec_.empty()) return std::make_tuple(h_string, cc_string); |
| |
| // First sort the encoding group vector according to the value of the |
| // discriminator bits. |
| std::sort(encoding_group_vec_.begin(), encoding_group_vec_.end(), |
| &InstructionGroupLess); |
| |
| std::string initializers; |
| // The signature for the top level decode function for this instruction |
| // group. |
| std::string signature = |
| absl::StrCat(opcode_enum_, " Decode", this->name(), "(", |
| format_->uint_type_name(), " inst_word)"); |
| std::string w_format_signature = absl::StrCat( |
| "std::pair<", opcode_enum_, ", FormatEnum> Decode", this->name(), |
| "WithFormat(", format_->uint_type_name(), " inst_word)"); |
| // First part of the definition of the top level decoder function. |
| std::string top_level_decoder = absl::StrCat(signature, " {\n"); |
| std::string w_format_top_level_decoder = |
| absl::StrCat(w_format_signature, " {\n"); |
| std::string declarations = |
| absl::StrCat("std::pair<", opcode_enum_, ", FormatEnum> Decode", |
| this->name(), "None(", format_->uint_type_name(), ");\n"); |
| std::string definitions = absl::StrCat( |
| "std::pair<", opcode_enum_, ", FormatEnum> Decode", this->name(), "None(", |
| format_->uint_type_name(), ") {\n return std::make_pair(", opcode_enum_, |
| "::kNone, FormatEnum::kNone);\n}\n\n"); |
| for (size_t i = 0; i < encoding_group_vec_.size(); i++) { |
| auto* grp = encoding_group_vec_[i]; |
| std::string name = absl::StrCat(this->name(), "_", absl::Hex(i)); |
| grp->EmitInitializers(name, &initializers, opcode_enum_); |
| grp->EmitDecoders(name, &declarations, &definitions, opcode_enum_); |
| absl::StrAppend(&top_level_decoder, " auto opcode = Decode", name, |
| "(inst_word).first;\n"); |
| absl::StrAppend(&w_format_top_level_decoder, |
| " auto opcode_format = Decode", name, "(inst_word);\n"); |
| if (encoding_group_vec_.size() > 1) { |
| absl::StrAppend(&top_level_decoder, " if (opcode != ", opcode_enum_, |
| "::kNone) return opcode;\n"); |
| absl::StrAppend(&w_format_top_level_decoder, |
| " if (opcode_format.first != ", opcode_enum_, |
| "::kNone) return opcode_format;\n"); |
| } |
| } |
| // Last part of the definition of the top level decoder function. |
| absl::StrAppend(&top_level_decoder, |
| " return opcode;\n" |
| "}\n"); |
| absl::StrAppend(&w_format_top_level_decoder, |
| " return opcode_format;\n" |
| "}\n"); |
| // String the different strings together in order and return. |
| absl::StrAppend(&cc_string, declarations, initializers, definitions, |
| top_level_decoder, w_format_top_level_decoder); |
| absl::StrAppend(&h_string, signature, ";\n", w_format_signature, ";\n"); |
| return std::make_tuple(h_string, cc_string); |
| } |
| |
| // Emit code to encode the instructions in the group. |
| void InstructionGroup::GetInstructionEncodings( |
| absl::btree_map<std::string, std::tuple<uint64_t, int>>& encodings) { |
| for (auto* enc : encoding_vec_) { |
| encodings.insert(std::make_pair(ToPascalCase(enc->name()), |
| std::make_tuple(enc->GetValue(), width()))); |
| } |
| } |
| |
| // Write out instruction group information. |
| std::string InstructionGroup::WriteGroup() { |
| std::string output; |
| absl::StrAppend(&output, "\n\n// Instruction group: ", name_, "\n"); |
| absl::PadSpec pad; |
| switch (width_ / 8) { |
| case 1: |
| pad = absl::PadSpec::kZeroPad2; |
| break; |
| case 2: |
| pad = absl::PadSpec::kZeroPad4; |
| break; |
| case 4: |
| pad = absl::PadSpec::kZeroPad8; |
| break; |
| case 8: |
| pad = absl::PadSpec::kZeroPad16; |
| break; |
| default: |
| pad = absl::PadSpec::kNoPad; |
| break; |
| } |
| uint64_t common_mask = 0xffff'ffff'ffff'ffff; |
| for (auto& [key, unused] : encoding_map_) { |
| common_mask &= key; |
| } |
| absl::StrAppend(&output, "// common bits: ", absl::Hex(common_mask, pad), |
| "\n"); |
| for (auto* grp : encoding_group_vec_) { |
| absl::StrAppend(&output, grp->DumpGroup("", " ")); |
| } |
| return output; |
| } |
| |
| } // namespace bin_format |
| } // namespace decoder |
| } // namespace sim |
| } // namespace mpact |