| // 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. |
| |
| #ifndef MPACT_SIM_DECODER_OPCODE_H_ |
| #define MPACT_SIM_DECODER_OPCODE_H_ |
| |
| #include <functional> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/btree_set.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "mpact/sim/decoder/format_name.h" |
| #include "mpact/sim/decoder/resource.h" |
| #include "mpact/sim/decoder/template_expression.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace machine_description { |
| namespace instruction_set { |
| |
| // An instance of the Opcode class represents an individual instruction opcode |
| // in the instruction set. The Opcode name has to be unique within the |
| // instruction set. In addition to having a name, the opcode also has an |
| // optional predicate operand name, a (possibly empty) list of source operand |
| // names, and a (possibly empty) list of destination operand names. |
| // |
| // Opcodes are created using the OpcodeFactory factory class. Each opcode is |
| // assigned a unique (within the OpcodeFactory) value that is used to define |
| // the value of the corresponding class enum entry in the generated code. This |
| // value is unrelated to any value of the "opcode" field in the instruction |
| // encoding. |
| class DestinationOperand { |
| public: |
| // Operand latency is defined by the expression. |
| DestinationOperand(std::string name, TemplateExpression *expression) |
| : name_(std::move(name)), |
| pascal_case_name_(ToPascalCase(name_)), |
| expression_(expression) {} |
| // Operand latency is a constant. |
| DestinationOperand(std::string name, int latency) |
| : name_(std::move(name)), |
| pascal_case_name_(ToPascalCase(name_)), |
| expression_(new TemplateConstant(latency)) {} |
| // This constructor is used when the destination operand latency is specified |
| // as '*' - meaning that it will be computed at the time of decode. |
| explicit DestinationOperand(std::string name) |
| : name_(std::move(name)), |
| pascal_case_name_(ToPascalCase(name_)), |
| expression_(nullptr) {} |
| ~DestinationOperand() { delete expression_; } |
| |
| const std::string &name() const { return name_; } |
| const std::string &pascal_case_name() const { return pascal_case_name_; } |
| TemplateExpression *expression() const { return expression_; } |
| bool HasLatency() const { return expression_ != nullptr; } |
| absl::StatusOr<int> GetLatency() const { |
| if (expression_ == nullptr) return -1; |
| auto res = expression_->GetValue(); |
| if (!res.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Template expression evaluation error", res.status().message())); |
| } |
| auto variant_value = res.value(); |
| auto *value_ptr = std::get_if<int>(&variant_value); |
| if (value_ptr == nullptr) { |
| return absl::InternalError("Template expression type error"); |
| } |
| return *value_ptr; |
| } |
| |
| private: |
| std::string name_; |
| std::string pascal_case_name_; |
| TemplateExpression *expression_; |
| }; |
| |
| // This struct is used to specify the location of an operand within an |
| // instruction. It specifies which instruction (or child instruction) number. In |
| // this case, 0 is the top level instruction, 1 is the first child instruction |
| // etc. The type is 'p' for predicate operand, 's' for source operand, and 'd' |
| // for destination operand. The instance number specifies the entry index in the |
| // source or destination operand vector. |
| struct OperandLocator { |
| int op_spec_number; |
| char type; |
| int instance; |
| OperandLocator(int op_spec_number_, char type_, int instance_) |
| : op_spec_number(op_spec_number_), type(type_), instance(instance_) {} |
| }; |
| |
| struct FormatInfo { |
| FormatInfo() = default; |
| // Use default copy constructor. |
| FormatInfo(const FormatInfo &) = default; |
| std::string op_name; |
| bool is_formatted = true; |
| std::string number_format; |
| bool use_address = false; |
| std::string operation; |
| bool do_left_shift = false; |
| int shift_amount = 0; |
| }; |
| |
| struct DisasmFormat { |
| DisasmFormat() = default; |
| // Copy constructor. |
| DisasmFormat(const DisasmFormat &df) { |
| width = df.width; |
| for (auto const &frag : df.format_fragment_vec) { |
| format_fragment_vec.push_back(frag); |
| } |
| for (auto const *info : df.format_info_vec) { |
| format_info_vec.push_back(new FormatInfo(*info)); |
| } |
| } |
| // Destructor. |
| ~DisasmFormat() { |
| for (auto *info : format_info_vec) { |
| delete info; |
| } |
| format_info_vec.clear(); |
| } |
| int width = 0; |
| std::vector<std::string> format_fragment_vec; |
| std::vector<FormatInfo *> format_info_vec; |
| }; |
| |
| struct ResourceReference { |
| Resource *resource; |
| DestinationOperand *dest_op; |
| TemplateExpression *begin_expression; |
| TemplateExpression *end_expression; |
| ResourceReference(Resource *resource_, DestinationOperand *dest_op_, |
| TemplateExpression *begin_expr_, |
| TemplateExpression *end_expr_) |
| : resource(resource_), |
| dest_op(dest_op_), |
| begin_expression(begin_expr_), |
| end_expression(end_expr_) {} |
| ResourceReference(const ResourceReference &) = default; |
| }; |
| |
| using OpLocatorMap = absl::flat_hash_map<std::string, OperandLocator>; |
| |
| class Opcode { |
| friend class OpcodeFactory; |
| |
| public: |
| // Constructor is private (defined below), only used by OpcodeFactory. |
| virtual ~Opcode(); |
| |
| // Each opcode specifies an optional predicate operand name, an optional list |
| // of source operand names, and an optional list of destination operand names. |
| // These methods append the source and destination operand names to the |
| // opcode. These names are used to create interface methods that are called |
| // to get the Predicate, Source and Destination operand interfaces (defined |
| // in .../sim/generic/operand_interfaces.h. The implementation of these |
| // methods will be left to the user of this generator tool. |
| void AppendSourceOpName(absl::string_view op_name); |
| void AppendDestOp(absl::string_view op_name, TemplateExpression *expression); |
| void AppendDestOp(absl::string_view op_name); |
| DestinationOperand *GetDestOp(absl::string_view op_name); |
| // Append child opcode specification. |
| void AppendChild(Opcode *op) { child_ = op; } |
| // Checks destination latencies with the given function. Returns true if all |
| // comply. |
| bool ValidateDestLatencies(const std::function<bool(int)> &validator) const; |
| int instruction_size() const { return instruction_size_; } |
| void set_instruction_size(int val) { instruction_size_ = val; } |
| Opcode *child() const { return child_; } |
| Opcode *parent() const { return parent_; } |
| const std::string &name() const { return name_; } |
| const std::string &pascal_name() const { return pascal_name_; } |
| // Each opcode is assigned a unique value that is used in the slot class |
| // enum definition. |
| int value() const { return value_; } |
| // Predicate, source and destination operand names. |
| const std::string &predicate_op_name() const { return predicate_op_name_; } |
| void set_predicate_op_name(absl::string_view op_name) { |
| predicate_op_name_ = op_name; |
| } |
| const std::vector<std::string> &source_op_name_vec() const { |
| return source_op_name_vec_; |
| } |
| const std::vector<DestinationOperand *> &dest_op_vec() const { |
| return dest_op_vec_; |
| } |
| |
| OpLocatorMap &op_locator_map() { return op_locator_map_; } // not const. |
| const OpLocatorMap &op_locator_map() const { return op_locator_map_; } |
| |
| private: |
| Opcode(absl::string_view name, int value); |
| int instruction_size_; |
| Opcode *child_ = nullptr; |
| Opcode *parent_ = nullptr; |
| std::string predicate_op_name_; |
| std::vector<std::string> source_op_name_vec_; |
| std::vector<DestinationOperand *> dest_op_vec_; |
| absl::flat_hash_map<std::string, DestinationOperand *> dest_op_map_; |
| std::string name_; |
| std::string pascal_name_; |
| std::string semfunc_code_string_; |
| int value_; |
| OpLocatorMap op_locator_map_; |
| }; |
| |
| class OpcodeFactory { |
| public: |
| OpcodeFactory() = default; |
| ~OpcodeFactory() = default; |
| |
| // If the opcode doesn't yet exist, create a new opcode and return the |
| // pointer, otherwise return an error code. |
| absl::StatusOr<Opcode *> CreateOpcode(absl::string_view name); |
| Opcode *CreateDefaultOpcode(); |
| absl::StatusOr<Opcode *> CreateChildOpcode(Opcode *opcode) const; |
| // Duplicate the opcode, but evaluate the destination latency expressions |
| // with the template argument expression vector. |
| absl::StatusOr<Opcode *> CreateDerivedOpcode(const Opcode *opcode, |
| TemplateInstantiationArgs *args); |
| const std::vector<Opcode *> &opcode_vec() const { return opcode_vec_; } |
| |
| private: |
| absl::btree_set<std::string> opcode_names_; |
| std::vector<Opcode *> opcode_vec_; |
| int opcode_value_ = 1; |
| }; |
| |
| } // namespace instruction_set |
| } // namespace machine_description |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // MPACT_SIM_DECODER_OPCODE_H_ |