This adds the ability to specify an instruction encoding as specializing that of another.

In some cases, there is a desire to specialize some instructions with specific operand
values to different mnemonics, even though the decoding of that instruction would match
the "general" instruction. For instance, in RiscV the 'rdcycle t5' instruction is the same
as 'csrrs t5, zero, cycle', or even 'csrrs t5, cycle'. The new capability allows this
to be expressed in the bin_fmt file as:

  rdcycle : specializes csrrs_nw : u_imm12 == 0xc00;

Which means that rdcycle has the same encoding as csrrs_nw, with one added constraint.
The generated decoder will now contain code to check for rdcycle whenever a csrrs_nw
instruction is matched, and prefer to return that opcode as opposed to the more generic
csrrs_nw opcode. Multiple such specializations can be added for an opcode. There is no
order in which they are considered, except that the base opcode will be considered last.

PiperOrigin-RevId: 706018484
Change-Id: I2a5393a33d96ab3023b04defef24dd83111f527c
diff --git a/mpact/sim/decoder/BinFormat.g4 b/mpact/sim/decoder/BinFormat.g4
index 912fe0a..c09440f 100644
--- a/mpact/sim/decoder/BinFormat.g4
+++ b/mpact/sim/decoder/BinFormat.g4
@@ -130,6 +130,7 @@
   : name=IDENT ':' format_name=IDENT ':' field_constraint_list ';'
   | generate=GENERATE '(' range_assignment (',' range_assignment)* ')'
     '{' generator_instruction_def_list '}' ';'
+  | name=IDENT ':' SPECIALIZES parent=IDENT ':' field_constraint_list ';'
   ;
 
 
@@ -257,6 +258,7 @@
 INSTRUCTION : 'instruction';
 GROUP : 'group';
 DECODER : 'decoder';
+SPECIALIZES : 'specializes';
 
 // Other tokens.
 STRING_LITERAL : UNTERMINATED_STRING_LITERAL '"';
diff --git a/mpact/sim/decoder/bin_format_visitor.cc b/mpact/sim/decoder/bin_format_visitor.cc
index d278ca2..ccd01d6 100644
--- a/mpact/sim/decoder/bin_format_visitor.cc
+++ b/mpact/sim/decoder/bin_format_visitor.cc
@@ -171,6 +171,8 @@
   if (error_listener_->HasError() > 0) {
     return absl::InternalError("Errors encountered - terminating.");
   }
+  // Process specializations.
+  ProcessSpecializations(encoding_info.get());
 
   // Create output streams for .h and .cc files.
   std::string dot_h_name = absl::StrCat(prefix, "_bin_decoder.h");
@@ -779,11 +781,19 @@
 void BinFormatVisitor::VisitInstructionDef(InstructionDefCtx *ctx,
                                            InstructionGroup *inst_group) {
   if (ctx == nullptr) return;
-
+  // If it is a generator, process it.
   if (ctx->generate != nullptr) {
     ProcessInstructionDefGenerator(ctx, inst_group);
     return;
   }
+  // Check to see if it is a specialization. If it is a specialization, save it
+  // for later processing when all the instructions have been processed.
+  int file_index = context_file_map_.at(ctx);
+  if (ctx->parent != nullptr) {
+    specializations_.push_back(ctx);
+    context_file_map_.insert({ctx, file_index});
+    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();
@@ -813,7 +823,6 @@
       inst_group->AddInstructionEncoding(ctx->format_name, name, format);
   if (format == nullptr) return;
   // Add constraints to the instruction encoding.
-  int file_index = context_file_map_.at(ctx);
   for (auto *constraint : ctx->field_constraint_list()->field_constraint()) {
     context_file_map_.insert({constraint, file_index});
     VisitConstraint(format, constraint, inst_encoding);
@@ -1223,6 +1232,33 @@
   return parent_group;
 }
 
+void BinFormatVisitor::ProcessSpecializations(BinEncodingInfo *encoding_info) {
+  for (auto *ctx : specializations_) {
+    auto file_index = context_file_map_.at(ctx);
+    std::string name = ctx->name->getText();
+    std::string parent_name = ctx->parent->getText();
+    for (auto &[unused, grp_ptr] : encoding_info->instruction_group_map()) {
+      auto iter = grp_ptr->encoding_name_map().find(parent_name);
+      if (iter != grp_ptr->encoding_name_map().end()) {
+        auto *parent_encoding = iter->second;
+        auto *format = parent_encoding->format();
+        auto *inst_encoding = new InstructionEncoding(name, format);
+        for (auto *constraint :
+             ctx->field_constraint_list()->field_constraint()) {
+          context_file_map_.insert({constraint, file_index});
+          VisitConstraint(format, constraint, inst_encoding);
+        }
+        if (!grp_ptr->AddSpecialization(name, parent_name, inst_encoding)
+                 .ok()) {
+          delete inst_encoding;
+          continue;
+        }
+        break;
+      }
+    }
+  }
+}
+
 }  // namespace bin_format
 }  // namespace decoder
 }  // namespace sim
diff --git a/mpact/sim/decoder/bin_format_visitor.h b/mpact/sim/decoder/bin_format_visitor.h
index f6e9e63..f10455a 100644
--- a/mpact/sim/decoder/bin_format_visitor.h
+++ b/mpact/sim/decoder/bin_format_visitor.h
@@ -133,6 +133,7 @@
       const std::string &group_name, GroupNameListCtx *ctx,
       absl::flat_hash_set<std::string> &group_name_set,
       BinEncodingInfo *encoding_info);
+  void ProcessSpecializations(BinEncodingInfo *encoding_info);
 
   // Accessors.
   decoder::DecoderErrorListener *error_listener() const {
@@ -163,6 +164,8 @@
   std::vector<BinFmtAntlrParserWrapper *> antlr_parser_wrappers_;
   // Map from comparator string to constraint type.
   absl::flat_hash_map<std::string, ConstraintType> constraint_string_to_type_;
+  // Specializations to process after all instructions have been processed.
+  std::vector<InstructionDefCtx *> specializations_;
 };
 
 }  // namespace bin_format
diff --git a/mpact/sim/decoder/encoding_group.cc b/mpact/sim/decoder/encoding_group.cc
index 9238df1..30d203f 100644
--- a/mpact/sim/decoder/encoding_group.cc
+++ b/mpact/sim/decoder/encoding_group.cc
@@ -118,6 +118,7 @@
   for (auto *enc : encoding_vec_) {
     if (!enc->other_constraints().empty()) return false;
     if (!enc->equal_extracted_constraints().empty()) return false;
+    if (!enc->HasSpecialization()) return false;
   }
   return discriminator_ == varying_;
 }
@@ -504,6 +505,16 @@
                   definitions_ptr);
   EmitExtractions(indent, encoding->other_constraints(), extracted,
                   definitions_ptr);
+  // Write any field/overlay extractions needed for the specializations (that
+  // haven't already been extracted).
+  for (auto const &[unused, encoding] : encoding->specializations()) {
+    EmitExtractions(indent, encoding->equal_constraints(), extracted,
+                    definitions_ptr);
+    EmitExtractions(indent, encoding->equal_extracted_constraints(), extracted,
+                    definitions_ptr);
+    EmitExtractions(indent, encoding->other_constraints(), extracted,
+                    definitions_ptr);
+  }
   // Construct the if statement.
   std::string condition;
   std::string connector;
@@ -516,19 +527,62 @@
   count += EmitOtherConstraintConditions(encoding->other_constraints(),
                                          connector, &condition);
 
+  bool specialization = encoding->HasSpecialization();
   // Ensure the number of parentheses are appropriate to the number of
   // conjunctions in the if statement.
   if (count > 1) {
-    absl::StrAppend(definitions_ptr, indent_str, "if (", condition, ")\n");
+    absl::StrAppend(definitions_ptr, indent_str, "if (", condition,
+                    specialization ? ") {\n" : ")\n");
     indent_str.append("  ");
   } else if (count == 1) {
-    absl::StrAppend(definitions_ptr, indent_str, "if ", condition, "\n");
+    absl::StrAppend(definitions_ptr, indent_str, "if ", condition,
+                    specialization ? " {\n" : "\n");
+    indent_str.append("  ");
+  }
+  // If the instruction has specializations, emit the if statement for each
+  // specialization.
+  std::string if_str = "if ";
+  if (specialization) {
+    for (auto const &[name, encoding] : encoding->specializations()) {
+      int spec_count = 0;
+      std::string spec_condition;
+      std::string spec_connector;
+      // Construct the condition for the specialization instruction.
+      spec_count += EmitConstraintConditions(
+          encoding->equal_constraints(), "==", spec_connector, &spec_condition);
+      spec_count +=
+          EmitConstraintConditions(encoding->equal_extracted_constraints(),
+                                   "==", spec_connector, &spec_condition);
+      spec_count += EmitOtherConstraintConditions(
+          encoding->other_constraints(), spec_connector, &spec_condition);
+      if (spec_count > 1) {
+        absl::StrAppend(definitions_ptr, indent_str, if_str, "(",
+                        spec_condition, ") {\n");
+        indent_str.append("  ");
+      } else if (spec_count == 1) {
+        absl::StrAppend(definitions_ptr, indent_str, if_str, spec_condition,
+                        " {\n");
+      }
+      absl::StrAppend(definitions_ptr, indent_str, "  return std::make_pair(",
+                      opcode_enum, "::k", ToPascalCase(encoding->name()),
+                      ", FormatEnum::k", ToPascalCase(encoding->format_name()),
+                      ");\n");
+      if_str = "} else if ";
+    }
+    absl::StrAppend(definitions_ptr, indent_str, "} else {\n");
     indent_str.append("  ");
   }
   absl::StrAppend(definitions_ptr, indent_str, "return std::make_pair(",
                   opcode_enum, "::k", ToPascalCase(encoding->name()),
                   ", FormatEnum::k", ToPascalCase(encoding->format_name()),
                   ");\n");
+  if (specialization) {
+    // Close the if statements.
+    indent_str = indent_str.substr(2);
+    absl::StrAppend(definitions_ptr, indent_str, "}\n");
+    indent_str = indent_str.substr(2);
+    absl::StrAppend(definitions_ptr, indent_str, "}\n");
+  }
   return count != 0 ? 1 : 0;
 }
 
@@ -599,8 +653,8 @@
   std::string indent_str(indent + 2, ' ');
   // Write any field/overlay extractions needed for the constraints.
   // Note, the extractions may be wider than the instruction word width, due
-  // to constant bits being added, so make sure to use appropriate type for each
-  // extraction.
+  // to constant bits being added, so make sure to use appropriate type for
+  // each extraction.
   for (auto const *constraint : constraints) {
     if (constraint->can_ignore) continue;
     if (constraint->field != nullptr) {
diff --git a/mpact/sim/decoder/instruction_encoding.cc b/mpact/sim/decoder/instruction_encoding.cc
index 9b91ff2..3fd17b7 100644
--- a/mpact/sim/decoder/instruction_encoding.cc
+++ b/mpact/sim/decoder/instruction_encoding.cc
@@ -16,6 +16,7 @@
 
 #include <cstdint>
 #include <string>
+#include <utility>
 
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
@@ -55,6 +56,25 @@
   }
 }
 
+InstructionEncoding::~InstructionEncoding() {
+  for (auto *constraint : equal_constraints_) {
+    delete constraint;
+  }
+  equal_constraints_.clear();
+  for (auto *constraint : equal_extracted_constraints_) {
+    delete constraint;
+  }
+  equal_extracted_constraints_.clear();
+  for (auto *constraint : other_constraints_) {
+    delete constraint;
+  }
+  other_constraints_.clear();
+  for (auto &[name, enc_ptr] : specializations_) {
+    delete enc_ptr;
+  }
+  specializations_.clear();
+}
+
 absl::StatusOr<Constraint *> InstructionEncoding::CreateConstraint(
     ConstraintType type, std::string lhs_name, std::string rhs_name) {
   Constraint constraint;
@@ -196,21 +216,6 @@
   return constraint;
 }
 
-InstructionEncoding::~InstructionEncoding() {
-  for (auto *constraint : equal_constraints_) {
-    delete constraint;
-  }
-  equal_constraints_.clear();
-  for (auto *constraint : equal_extracted_constraints_) {
-    delete constraint;
-  }
-  equal_extracted_constraints_.clear();
-  for (auto *constraint : other_constraints_) {
-    delete constraint;
-  }
-  other_constraints_.clear();
-}
-
 absl::Status InstructionEncoding::AddEqualConstraint(std::string field_name,
                                                      int64_t value) {
   // Invalidate the computed masks and values when a new constraint is added.
@@ -337,6 +342,20 @@
   return mask_ | extracted_mask_ | other_mask_;
 }
 
+absl::Status InstructionEncoding::AddSpecialization(
+    const std::string &name, InstructionEncoding *encoding) {
+  if (specializations_.contains(name)) {
+    format_->encoding_info()->error_listener()->semanticError(
+        nullptr, absl::StrCat("Duplicate instruction specialization name '",
+                              name, "' in format '", format_name_, "'."));
+    return absl::AlreadyExistsError(
+        absl::StrCat("Duplicate instruction specialization name '", name,
+                     "' in format '", format_name_, "'."));
+  }
+  specializations_.insert(std::make_pair(name, encoding));
+  return absl::OkStatus();
+}
+
 uint64_t InstructionEncoding::GetValue() {
   if (!mask_set_) {
     auto result = ComputeMaskAndValue();
diff --git a/mpact/sim/decoder/instruction_encoding.h b/mpact/sim/decoder/instruction_encoding.h
index 730e68b..6e7a246 100644
--- a/mpact/sim/decoder/instruction_encoding.h
+++ b/mpact/sim/decoder/instruction_encoding.h
@@ -19,6 +19,7 @@
 #include <string>
 #include <vector>
 
+#include "absl/container/btree_map.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "mpact/sim/decoder/bin_format_visitor.h"
@@ -76,6 +77,10 @@
   // Get the mask of the instruction word based on the bits in both equal and
   // not equal constraints.
   uint64_t GetCombinedMask();
+  // Add specialization to this encoding.
+  absl::Status AddSpecialization(const std::string &name,
+                                 InstructionEncoding *encoding);
+  bool HasSpecialization() const { return !specializations_.empty(); }
 
   // Accessors.
   const std::string &name() const { return name_; }
@@ -99,6 +104,13 @@
     return other_constraints_;
   }
 
+  const absl::btree_map<std::string, InstructionEncoding *> &specializations()
+      const {
+    return specializations_;
+  }
+
+  Format *format() const { return format_; }
+
  private:
   // Internal helper to create and check a constraint.
   absl::StatusOr<Constraint *> CreateConstraint(ConstraintType type,
@@ -122,6 +134,7 @@
   uint64_t other_mask_ = 0;
   uint64_t extracted_mask_ = 0;
   uint64_t value_ = 0;
+  absl::btree_map<std::string, InstructionEncoding *> specializations_;
 };
 
 }  // namespace bin_format
diff --git a/mpact/sim/decoder/instruction_group.cc b/mpact/sim/decoder/instruction_group.cc
index ca0018c..4b4e394 100644
--- a/mpact/sim/decoder/instruction_group.cc
+++ b/mpact/sim/decoder/instruction_group.cc
@@ -21,6 +21,7 @@
 #include <tuple>
 #include <utility>
 
+#include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "antlr4-runtime/Token.h"
 #include "mpact/sim/decoder/bin_encoding_info.h"
@@ -78,24 +79,24 @@
   // 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_set_.contains(name)) {
+  if (encoding_name_map_.contains(name)) {
     encoding_info_->error_listener()->semanticWarning(
         token, absl::StrCat("Duplicate instruction opcode name '", name,
                             "' in group '", this->name(), "'."));
   }
-  encoding_name_set_.insert(name);
   auto *encoding = new InstructionEncoding(name, format);
   encoding_vec_.push_back(encoding);
+  encoding_name_map_.insert(std::make_pair(name, encoding));
   return encoding;
 }
 
 void InstructionGroup::AddInstructionEncoding(InstructionEncoding *encoding) {
-  if (encoding_name_set_.contains(encoding->name())) {
+  if (encoding_name_map_.contains(encoding->name())) {
     encoding_info_->error_listener()->semanticWarning(
         nullptr, absl::StrCat("Duplicate instruction opcode name '",
                               encoding->name(), "' in group '", name(), "'."));
   }
-  encoding_name_set_.insert(encoding->name());
+  encoding_name_map_.insert(std::make_pair(encoding->name(), encoding));
   encoding_vec_.push_back(encoding);
 }
 
@@ -139,6 +140,23 @@
   }
 }
 
+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;
diff --git a/mpact/sim/decoder/instruction_group.h b/mpact/sim/decoder/instruction_group.h
index 2f7fb77..40459e2 100644
--- a/mpact/sim/decoder/instruction_group.h
+++ b/mpact/sim/decoder/instruction_group.h
@@ -21,7 +21,8 @@
 #include <vector>
 
 #include "absl/container/btree_map.h"
-#include "absl/container/flat_hash_set.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
 #include "antlr4-runtime/Token.h"
 #include "mpact/sim/decoder/bin_encoding_info.h"
 #include "mpact/sim/decoder/encoding_group.h"
@@ -59,6 +60,10 @@
   // Return a string containing information about this instruction group and
   // how it has been partitioned across encoding groups.
   std::string WriteGroup();
+  // Add a specialization to this instruction group.
+  absl::Status AddSpecialization(const std::string &name,
+                                 const std::string &parent_name,
+                                 InstructionEncoding *encoding);
 
   // Accessors.
   const std::string &name() const { return name_; }
@@ -69,6 +74,12 @@
   }
   int width() const { return width_; }
   BinEncodingInfo *encoding_info() const { return encoding_info_; }
+  const absl::flat_hash_map<std::string, InstructionEncoding *> &
+  encoding_name_map() const {
+    return encoding_name_map_;
+  }
+
+  Format *format() const { return format_; }
 
  private:
   std::string name_;
@@ -78,7 +89,7 @@
   std::string opcode_enum_;
   BinEncodingInfo *encoding_info_;
   std::vector<InstructionEncoding *> encoding_vec_;
-  absl::flat_hash_set<std::string> encoding_name_set_;
+  absl::flat_hash_map<std::string, InstructionEncoding *> encoding_name_map_;
   absl::btree_multimap<uint64_t, InstructionEncoding *> encoding_map_;
   std::vector<EncodingGroup *> encoding_group_vec_;
 };
diff --git a/mpact/sim/decoder/test/testfiles/instruction_group.bin_fmt b/mpact/sim/decoder/test/testfiles/instruction_group.bin_fmt
index 95eb73d..ff97c54 100644
--- a/mpact/sim/decoder/test/testfiles/instruction_group.bin_fmt
+++ b/mpact/sim/decoder/test/testfiles/instruction_group.bin_fmt
@@ -282,6 +282,12 @@
   csrrwi_nr: IType  : func3 == 0b101, rd == 0,  opcode == 0b111'0011;
   csrrsi_nw: IType  : func3 == 0b110, rs1 == 0, opcode == 0b111'0011;
   csrrci_nw: IType  : func3 == 0b111, rs1 == 0, opcode == 0b111'0011;
+  rdcycle    : specializes csrrs_nw : imm12 == 0xc00;
+  rdtime     : specializes csrrs_nw : imm12 == 0xc01;
+  rdinstret  : specializes csrrs_nw : imm12 == 0xc02;
+  rdcycleh   : specializes csrrs_nw : imm12 == 0xc80;
+  rdtimeh    : specializes csrrs_nw : imm12 == 0xc81;
+  rdinstreth : specializes csrrs_nw : imm12 == 0xc82;
 };
 
 // RiscV32 Privileged instructions.