blob: 2bf452739adbf57d03796612f3d208c55c9eb76d [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_set.h"
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/strings/string_view.h"
#include "mpact/sim/decoder/bundle.h"
#include "mpact/sim/decoder/format_name.h"
#include "mpact/sim/decoder/instruction.h"
#include "mpact/sim/decoder/opcode.h"
#include "mpact/sim/decoder/slot.h"
namespace mpact {
namespace sim {
namespace machine_description {
namespace instruction_set {
absl::btree_set<std::string> *InstructionSet::attribute_names_ = nullptr;
InstructionSet::InstructionSet(absl::string_view name)
: opcode_factory_(std::make_unique<OpcodeFactory>()),
resource_factory_(std::make_unique<ResourceFactory>()),
name_(name),
pascal_name_(ToPascalCase(name)) {}
InstructionSet::~InstructionSet() {
delete bundle_;
for (auto &[unused, bundle_ptr] : bundle_map_) {
delete bundle_ptr;
}
for (auto &[unused, slot_ptr] : slot_map_) {
delete slot_ptr;
}
bundle_map_.clear();
slot_map_.clear();
}
void InstructionSet::AddBundle(Bundle *bundle) {
bundle_map_.insert({bundle->name(), bundle});
}
void InstructionSet::AddSlot(Slot *slot) {
slot_map_.insert({slot->name(), slot});
}
// Lookup bundle and slot by name.
Bundle *InstructionSet::GetBundle(absl::string_view bundle_name) const {
auto iter = bundle_map_.find(bundle_name);
if (iter == bundle_map_.end()) return nullptr;
return iter->second;
}
Slot *InstructionSet::GetSlot(absl::string_view slot_name) const {
auto iter = slot_map_.find(slot_name);
if (iter == slot_map_.end()) return nullptr;
return iter->second;
}
absl::Status InstructionSet::AnalyzeResourceUse() {
for (auto const *slot : slot_order_) {
for (auto &[unused, inst_ptr] : slot->instruction_map()) {
for (auto const *def : inst_ptr->resource_acquire_vec()) {
if (def->begin_expression != nullptr) {
auto result = def->begin_expression->GetValue();
if (!result.ok()) return result.status();
if (std::get<int>(result.value()) != 0) {
def->resource->set_is_simple(false);
}
}
if (def->end_expression != nullptr) {
auto result = def->end_expression->GetValue();
if (!result.ok()) return result.status();
}
}
}
}
return absl::OkStatus();
}
void InstructionSet::ComputeSlotAndBundleOrders() {
// Compute order of slot definitions
for (auto const &[unused, slot_ptr] : slot_map_) {
if (slot_ptr->is_marked()) continue;
AddToSlotOrder(slot_ptr);
}
// Compute order of bundle definitions
for (auto const &[unused, bundle_ptr] : bundle_map_) {
if (bundle_ptr->is_marked()) continue;
AddToBundleOrder(bundle_ptr);
}
}
void InstructionSet::AddToBundleOrder(Bundle *bundle) {
if (bundle->is_marked()) return;
for (auto const &bundle_name : bundle->bundle_names()) {
Bundle *sub_bundle = bundle_map_[bundle_name];
AddToBundleOrder(sub_bundle);
}
bundle_order_.push_back(bundle);
bundle->set_is_marked(true);
}
void InstructionSet::AddToSlotOrder(Slot *slot) {
if (slot->is_marked()) return;
for (auto const &base_slot : slot->base_slots()) {
AddToSlotOrder(slot_map_[base_slot.base->name()]);
}
slot->set_is_marked(true);
slot_order_.push_back(slot);
}
void InstructionSet::AddAttributeName(const std::string &name) {
if (attribute_names_ == nullptr) {
attribute_names_ = new absl::btree_set<std::string>();
}
auto iter = attribute_names_->find(name);
if (iter == attribute_names_->end()) {
attribute_names_->insert(name);
}
}
// Return a string containing class header file, for all bundles and slots.
// As an example the following shows an abbreviated example of code that is
// generated.
//
// class MyEncodingType {
// public:
// virtual ~MyEncodingType() = default;
//
// virtual OpcodeEnum GetOpcode(SlotEnum slot, int entry) = 0;
// virtual ResourceOperandInterface *GetSimpleResource(
// SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// SimpleResourceEnum resource) = 0;
// virtual ResourceOperandInterface *GetComplexResource(
// SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// ComplexResourceEnum) = 0;
// virtual PredicateOperandInterface *GetPredicate(SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// PredOpEnum pred) = 0;
// virtual SourceOperandInterface *GetSource(SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// SourceOpEnum source,
// int source_no) = 0;
// virtual DestinationOperandInterface *GetDestination(int latency,
// SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// DestOpEnum dest,
// int dest_no) = 0;
// virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
// DestOpEnum dest) = 0;
// virtual ResourceOperandInterface *GetSimpleResourceOperand(
// SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// SimpleResourceVector resource_vec,
// int end) = 0;
// virtual ResourceOperandInterface *GetComplexResourceOperand(
// SlotEnum slot,
// int entry,
// OpcodeEnum opcode,
// ComplexResourceEnum resource,
// int begin,
// int end) = 0;
// };
//
// // Alu0 Slot class.
// class ScalarAlu0Slot {
// public:
// explicit ScalarAlu0Slot(ArchState *arch_state);
// virtual ~ScalarAlu0Slot() = default;
// virtual SemFunc GetNopSemFunc() = 0;
// virtual SemFunc GetSvctS32F32SemFunc() = 0;
// virtual SemFunc GetScvtF32S32SemFunc() = 0;
// ...
// Instruction *Decode(uint64_t address, MyEncodingType* isa_encoding,
// MyEncodingType::SlotEnum, int entry);
//
// private:
// ArchState *arch_state_;
// SemFuncGetters semantic_function_getter_;
// OperandSetters set_operands_;
// static constexpr MyEncodingType::SlotEnum slot_ =
// MyEncodingType::SlotEnum::kScalarAlu0;
// };
//
// class ScalarBundleDecoder {
// public:
// explicit ScalarBundleDecoder(ArchState *arch_state);
// virtual ~ScalarBundleDecoder() = default;
// virtual Instruction *Decode(uint64_t address, MyEncodingType *encoding);
// virtual SemFunc GetSemanticFunction() = 0;
//
// ScalarAlu0Slot *scalar_alu_0_decoder() {
// return scalar_alu_0_decoder_.get();
// }
// ScalarAlu1Slot *scalar_alu_1_decoder() {
// return scalar_alu_1_decoder_.get();
// }
//
// private:
// std::unique_ptr<ScalarAlu0Slot> scalar_alu_0_decoder_;
// std::unique_ptr<ScalarAlu1Slot> scalar_alu_1_decoder_;
// ArchState *arch_state_;
// };
//
// class InstructionSetFactory{
// public:
// InstructionSetFactory() = default;
// virtual ~InstructionSetFactory() = default;
// virtual std::unique_ptr<ScalarBundleDecoder> CreateScalarBundleDecoder(
// ArchState *) = 0;
// virtual std::unique_ptr<ScalarAlu0Slot> CreateScalarAlu0Slot(
// ArchState *) = 0;
// virtual std::unique_ptr<ScalarAlu1Slot> CreateScalarAlu1Slot(
// ArchState *) = 0;
// };
//
// class InstructionSet {
// public:
// InstructionSet(ArchState *arch_state, InstructionSetFactory *factory);
// Instruction *Decode(uint64_t address, MyEncodingType *encoding);
//
// private:
// std::unique_ptr<ScalarBundleDecoder> scalar_bundle_decoder_;
// ArchState *arch_state_;
// };
std::string InstructionSet::GenerateClassDeclarations(
absl::string_view file_name, absl::string_view opcode_file_name,
absl::string_view encoding_type) const {
std::string output;
std::string factory_class_name = pascal_name() + "InstructionSetFactory";
absl::StrAppend(&output, "class ", factory_class_name, ";\n");
for (auto const *slot : slot_order_) {
absl::StrAppend(&output, slot->GenerateClassDeclaration(encoding_type));
}
for (auto const *bundle : bundle_order_) {
absl::StrAppend(&output, bundle->GenerateClassDeclaration(encoding_type));
}
// Generate factory class.
absl::StrAppend(&output, "class ", factory_class_name,
"{\n"
" public:\n"
" ",
factory_class_name,
"() = default;\n"
" virtual ~",
factory_class_name, "() = default;\n");
for (auto const bundle : bundle_order_) {
if (bundle->is_marked()) {
std::string bundle_class = bundle->pascal_name() + "Decoder";
absl::StrAppend(&output, " virtual std::unique_ptr<", bundle_class,
"> Create", bundle_class, "(ArchState *) = 0;\n");
}
}
for (auto const slot : slot_order_) {
if (slot->is_referenced()) {
std::string slot_class = slot->pascal_name() + "Slot";
absl::StrAppend(&output, " virtual std::unique_ptr<", slot_class,
"> Create", slot_class, "(ArchState *) = 0;\n");
}
}
absl::StrAppend(&output,
"};\n"
"\n");
// Generate InstructionSet class.
absl::StrAppend(&output, "class ", pascal_name(),
"InstructionSet {\n"
" public:\n"
" ",
pascal_name(), "InstructionSet(ArchState *arch_state,\n",
Indent(absl::StrCat(" ", pascal_name(), "InstructionSet(")),
factory_class_name,
" *factory);\n"
" Instruction *Decode(uint64_t address, ",
encoding_type,
" *encoding);\n"
"\n"
" private:\n");
for (auto const &bundle_name : bundle_->bundle_names()) {
absl::StrAppend(&output, " std::unique_ptr<", ToPascalCase(bundle_name),
"Decoder> ", bundle_name, "_decoder_;\n");
}
for (auto const &[slot_name, unused] : bundle_->slot_uses()) {
absl::StrAppend(&output, " std::unique_ptr<", ToPascalCase(slot_name),
"Slot> ", slot_name, "_decoder_;\n");
}
absl::StrAppend(&output,
" ArchState *arch_state_;\n"
"};\n"
"\n");
return output;
}
// Return string containing .cc file for all bundles and slots.
std::string InstructionSet::GenerateClassDefinitions(
absl::string_view include_file, absl::string_view encoding_type) const {
std::string output;
std::string class_name = pascal_name() + "InstructionSet";
std::string factory_class_name = class_name + "Factory";
for (auto const *slot : slot_order_) {
absl::StrAppend(&output, slot->GenerateClassDefinition(encoding_type));
}
// Constructor.
absl::StrAppend(&output, class_name, "::", class_name,
"(ArchState *arch_state, ", factory_class_name,
"*factory) : \n"
" arch_state_(arch_state) {\n");
for (auto const &bundle_name : bundle_->bundle_names()) {
absl::StrAppend(&output, " ", bundle_name, "_decoder_ = factory->Create",
ToPascalCase(bundle_name), "Decoder(arch_state_);\n");
}
for (auto const &[slot_name, unused] : bundle_->slot_uses()) {
absl::StrAppend(&output, " ", slot_name, "_decoder_ = factory->Create",
ToPascalCase(slot_name), "Slot(arch_state_);\n");
}
absl::StrAppend(&output, "}\n");
// Generate the top level decode function body.
absl::StrAppend(&output, "Instruction *", class_name,
"::Decode(uint64_t address, ", encoding_type,
" *encoding) {\n"
" Instruction *inst = nullptr;"
" Instruction *tmp_inst;\n"
" bool success = false;\n"
" int size = 0;\n");
if (!bundle_->bundle_names().empty()) {
// If there are bundles, then a "parent instruction" is created. Bundles
// are added as children of this parent instruction. The parent instruction
// is responsible for controlling issue of the bundles.
absl::StrAppend(&output,
" inst = new Instruction(address, arch_state_);\n");
// Generate calls to each of the top level bundle Decode methods.
for (auto const &bundle_name : bundle_->bundle_names()) {
absl::StrAppend(&output, " tmp_inst = ", bundle_name,
"_decoder_->Decode(address, encoding);\n"
" inst->AppendChild(tmp_inst);\n"
" size += tmp_inst->size();\n"
" tmp_inst->DecRef();\n"
" success |= (nullptr != tmp_inst);\n");
}
}
// Generate calls to each of the top level slot Decode methods.
for (auto const &[slot_name, instance_vec] : bundle_->slot_uses()) {
std::string enum_name =
absl::StrCat("SlotEnum::", "k", ToPascalCase(slot_name));
if (instance_vec.empty()) {
absl::StrAppend(&output, " tmp_inst = ", slot_name,
"_decoder_->Decode(address, encoding, ", enum_name,
", 0);\n"
" if (tmp_inst != nullptr) size += tmp_inst->size();\n"
" if (inst == nullptr) {\n"
" inst = tmp_inst;\n"
" } else {\n"
" inst->Append(tmp_inst);\n"
" tmp_inst->DecRef();\n"
" }\n"
" success |= (nullptr != tmp_inst);\n");
} else {
for (auto const index : instance_vec) {
absl::StrAppend(&output, " tmp_inst = ", slot_name,
"_decoder_->Decode(address, encoding, , ", enum_name,
", ", index,
");\n"
" if (tmp_inst != nullptr) size += tmp_inst->size();\n"
" if (inst == nullptr) {\n"
" inst = tmp_inst;\n"
" } else {\n"
" inst->Append(tmp_inst);\n"
" tmp_inst->DecRef();\n"
" }\n"
" success |= (nullptr != tmp_inst);\n");
}
}
}
// If the decode failed, DecRef the instruction and return nullptr.
absl::StrAppend(&output,
" inst->set_size(size);\n"
" if (!success) {\n"
" inst->DecRef();\n"
" inst = nullptr;\n"
" }\n"
" return inst;\n"
"}\n");
return output;
}
InstructionSet::StringPair InstructionSet::GenerateEnums(
absl::string_view file_name) const {
std::string h_output;
std::string cc_output;
// Emit slot enumeration type.
absl::StrAppend(&h_output,
" enum class SlotEnum {\n"
" kNone = 0,\n");
absl::btree_set<std::string> name_set;
for (auto const *slot : slot_order_) {
if (slot->is_referenced()) name_set.insert(slot->pascal_name());
}
for (auto const &name : name_set) {
absl::StrAppend(&h_output, " k", name, ",\n");
}
absl::StrAppend(&h_output, " };\n\n");
// Btree sets to sort by name.
absl::btree_set<std::string> predicate_operands;
absl::btree_set<std::string> source_operands;
absl::btree_set<std::string> dest_operands;
absl::btree_set<std::string> dest_latency;
// Insert PascalCase operand names into the sets to select unique names.
for (auto const *slot : slot_order_) {
if (!slot->is_referenced()) continue;
for (auto const &[unused, inst_ptr] : slot->instruction_map()) {
auto *inst = inst_ptr;
while (inst != nullptr) {
auto *opcode = inst->opcode();
if (!opcode->predicate_op_name().empty()) {
predicate_operands.insert(ToPascalCase(opcode->predicate_op_name()));
}
for (auto const &source_op_name : opcode->source_op_name_vec()) {
source_operands.insert(ToPascalCase(source_op_name));
}
for (auto const *dest_op : opcode->dest_op_vec()) {
dest_operands.insert(dest_op->pascal_case_name());
if (dest_op->expression() == nullptr) {
dest_latency.insert(dest_op->pascal_case_name());
}
}
inst = inst->child();
}
}
}
// Create enum for predicate operands.
absl::StrAppend(&h_output, " enum class PredOpEnum {\n");
int pred_count = 0;
absl::StrAppend(&h_output, " kNone = ", pred_count++, ",\n");
for (auto const &pred_name : predicate_operands) {
absl::StrAppend(&h_output, " k", pred_name, " = ", pred_count++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", pred_count,
",\n"
" };\n\n");
// Create enum for source operands.
absl::StrAppend(&h_output, " enum class SourceOpEnum {\n");
int src_count = 0;
absl::StrAppend(&h_output, " kNone = ", src_count++, ",\n");
for (auto const &source_name : source_operands) {
absl::StrAppend(&h_output, " k", source_name, " = ", src_count++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", src_count,
",\n"
" };\n\n");
// Create enum for destination operands.
absl::StrAppend(&h_output, " enum class DestOpEnum {\n");
int dst_count = 0;
absl::StrAppend(&h_output, " kNone = ", dst_count++, ",\n");
for (auto const &dest_name : dest_operands) {
absl::StrAppend(&h_output, " k", dest_name, " = ", dst_count++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", dst_count,
",\n"
" };\n\n");
// Emit opcode enumeration type.
absl::StrAppend(&h_output,
" enum class OpcodeEnum {\n"
" kNone = 0,\n");
name_set.clear();
for (auto const *opcode : opcode_factory_->opcode_vec()) {
name_set.insert(opcode->pascal_name());
}
int opcode_value = 1;
for (auto const &name : name_set) {
absl::StrAppend(&h_output, " k", name, " = ", opcode_value++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", opcode_value, "\n");
absl::StrAppend(&h_output, " };\n\n");
// Emit array of opcode names.
absl::StrAppend(&cc_output,
"const char *kOpcodeNames[static_cast<int>("
"OpcodeEnum::kPastMaxValue)] = {\n"
" kNoneName,\n");
absl::StrAppend(&h_output, " constexpr char kNoneName[] = \"none\";\n");
for (auto const &name : name_set) {
absl::StrAppend(&h_output, " constexpr char k", name, "Name[] = \"", name,
"\";\n");
absl::StrAppend(&cc_output, " k", name, "Name,\n");
}
absl::StrAppend(&cc_output, "};\n\n");
absl::StrAppend(&h_output,
" extern const char *kOpcodeNames[static_cast<int>(\n"
" OpcodeEnum::kPastMaxValue)];\n\n");
// Emit resource enumeration types.
absl::StrAppend(&h_output,
" enum class SimpleResourceEnum {\n"
" kNone = 0,\n");
int resource_count = 1;
name_set.clear();
for (auto const &[unused, resource_ptr] : resource_factory_->resource_map()) {
if (resource_ptr->is_simple()) {
name_set.insert(resource_ptr->pascal_name());
}
}
for (auto const &name : name_set) {
absl::StrAppend(&h_output, " k", name, " = ", resource_count++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", resource_count,
"\n };\n\n");
absl::StrAppend(&h_output,
" enum class ComplexResourceEnum {\n"
" kNone = 0,\n");
resource_count = 1;
name_set.clear();
for (auto const &[unused, resource_ptr] : resource_factory_->resource_map()) {
if (!resource_ptr->is_simple()) {
name_set.insert(resource_ptr->pascal_name());
}
}
for (auto const &name : name_set) {
absl::StrAppend(&h_output, " k", name, " = ", resource_count++, ",\n");
}
absl::StrAppend(&h_output, " kPastMaxValue = ", resource_count,
"\n };\n\n");
// Emit instruction attribute types.
absl::StrAppend(&h_output, " enum class AttributeEnum {\n");
int attribute_count = 0;
if (InstructionSet::attribute_names_ != nullptr) {
for (auto const &name : *InstructionSet::attribute_names_) {
absl::StrAppend(&h_output, " k", ToPascalCase(name), " = ",
attribute_count++, ",\n");
}
}
absl::StrAppend(&h_output, " kPastMaxValue = ", attribute_count,
"\n };\n\n");
return {h_output, cc_output};
}
} // namespace instruction_set
} // namespace machine_description
} // namespace sim
} // namespace mpact