blob: e3684ea80a17c105429c60fbc795c3ae523bd591 [file]
// 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