blob: 2d3f8c1e6dce26ea1582f1a5b25d3fa54518fe08 [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_visitor.h"
#include <cctype>
#include <cstddef>
#include <filesystem> // NOLINT: third party.
#include <fstream>
#include <iostream>
#include <istream>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "antlr4-runtime/ParserRuleContext.h"
#include "mpact/sim/decoder/bundle.h"
#include "mpact/sim/decoder/decoder_error_listener.h"
#include "mpact/sim/decoder/format_name.h"
#include "mpact/sim/decoder/instruction_set.h"
#include "mpact/sim/decoder/instruction_set_contexts.h"
#include "mpact/sim/decoder/opcode.h"
#include "mpact/sim/decoder/slot.h"
#include "mpact/sim/decoder/template_expression.h"
#include "re2/re2.h"
namespace mpact {
namespace sim {
namespace machine_description {
namespace instruction_set {
static absl::StatusOr<TemplateValue> AbsoluteValueTemplateFunc(
TemplateInstantiationArgs *args) {
if (args->size() != 1) {
return absl::InternalError(absl::StrCat(
"Wrong number of arguments, expected 1, was given ", args->size()));
}
auto result = (*args)[0]->GetValue();
if (!result.ok()) return result.status();
auto *value_ptr = std::get_if<int>(&result.value());
if (value_ptr == nullptr) {
return absl::InternalError("Type mismatch - int expected");
}
int return_value = (*value_ptr < 0) ? -(*value_ptr) : *value_ptr;
return TemplateValue(return_value);
}
InstructionSetVisitor::InstructionSetVisitor() {
template_function_evaluators_.insert(std::make_pair(
"abs", TemplateFunctionEvaluator(AbsoluteValueTemplateFunc, 1)));
}
InstructionSetVisitor::~InstructionSetVisitor() {
for (auto &[unused, expr_ptr] : constant_map_) {
delete expr_ptr;
}
constant_map_.clear();
for (auto *wrapper : antlr_parser_wrappers_) {
delete wrapper;
}
antlr_parser_wrappers_.clear();
for (auto *expr : disasm_field_widths_) delete expr;
disasm_field_widths_.clear();
}
// Main entry point for processing the file.
absl::Status InstructionSetVisitor::Process(
const std::vector<std::string> &file_names, const std::string &prefix,
const std::string &isa_name, const std::vector<std::string> &include_roots,
absl::string_view directory) {
// Create and add the error listener.
set_error_listener(std::make_unique<decoder::DecoderErrorListener>());
if (isa_name.empty()) {
error_listener()->semanticError(nullptr, "Isa name cannot be empty");
return absl::InvalidArgumentError("Isa name cannot be empty");
}
for (auto &include_root : include_roots) {
include_dir_vec_.push_back(include_root);
}
std::string isa_prefix = prefix;
std::istream *source_stream = &std::cin;
if (!file_names.empty()) {
source_stream = new std::fstream(file_names[0], std::fstream::in);
}
// Create an antlr4 stream from the input stream.
IsaAntlrParserWrapper parser_wrapper(source_stream);
error_listener()->set_file_name(file_names[0]);
file_names_.push_back(file_names[0]);
parser_wrapper.parser()->removeErrorListeners();
parser_wrapper.parser()->addErrorListener(error_listener());
// Parse the file and then create the data structures.
TopLevelCtx *top_level = parser_wrapper.parser()->top_level();
if (!file_names.empty()) {
delete source_stream;
source_stream = nullptr;
}
if (error_listener()->HasError() > 0) {
return absl::InternalError("Errors encountered - terminating.");
}
// Visit the parse tree starting at the namespaces declaration.
VisitTopLevel(top_level);
// Process additional source files.
for (int i = 1; i < file_names.size(); ++i) {
ParseIncludeFile(top_level, file_names[i], {});
}
// Now process the parse tree.
auto instruction_set = ProcessTopLevel(isa_name);
// Include files may generate additional syntax errors.
if (instruction_set == nullptr) {
return absl::InternalError("Errors encountered - terminating.");
}
// Verify that all referenced bundles and slots were defined.
PerformBundleReferenceChecks(instruction_set.get(),
instruction_set->bundle());
if (error_listener()->HasError() > 0) {
return absl::InternalError("Errors encountered - terminating.");
}
// Analyze resource use and partition resources into simple and complex
// resources.
auto status = instruction_set->AnalyzeResourceUse();
if (!status.ok()) {
error_listener()->semanticError(nullptr, status.message());
}
// If the prefix is empty, use the source file name.
if (isa_prefix.empty() && file_names.empty()) {
error_listener()->semanticError(nullptr,
"No prefix or file name specified");
} else if (isa_prefix.empty()) {
isa_prefix =
ToSnakeCase(std::filesystem::path(file_names[0]).stem().string());
}
// Check for additional errors.
if (error_listener()->HasError() > 0) {
return absl::InternalError("Errors encountered - terminating.");
}
std::string encoding_type_name =
absl::StrCat(ToPascalCase(isa_name), "EncodingBase");
// Create output streams for .h and .cc files.
std::string dec_dot_h_name = absl::StrCat(isa_prefix, "_decoder.h");
std::string dec_dot_cc_name = absl::StrCat(isa_prefix, "_decoder.cc");
std::string enc_dot_h_name = absl::StrCat(isa_prefix, "_encoder.h");
std::string enc_dot_cc_name = absl::StrCat(isa_prefix, "_encoder.cc");
std::string enum_h_name = absl::StrCat(isa_prefix, "_enums.h");
std::string enum_cc_name = absl::StrCat(isa_prefix, "_enums.cc");
std::ofstream dec_dot_h_file(absl::StrCat(directory, "/", dec_dot_h_name));
std::ofstream dec_dot_cc_file(absl::StrCat(directory, "/", dec_dot_cc_name));
std::ofstream enc_dot_h_file(absl::StrCat(directory, "/", enc_dot_h_name));
std::ofstream enc_dot_cc_file(absl::StrCat(directory, "/", enc_dot_cc_name));
std::ofstream enum_h_file(absl::StrCat(directory, "/", enum_h_name));
std::ofstream enum_cc_file(absl::StrCat(directory, "/", enum_cc_name));
// Generate the code, close the files and return.
std::string guard_name = ToHeaderGuard(dec_dot_h_name);
// Decoder .h file.
dec_dot_h_file << GenerateHdrFileProlog(dec_dot_h_name, enum_h_name,
guard_name, encoding_type_name,
instruction_set->namespaces());
dec_dot_h_file << instruction_set->GenerateClassDeclarations(
dec_dot_h_name, enum_h_name, encoding_type_name);
dec_dot_h_file << GenerateHdrFileEpilog(guard_name,
instruction_set->namespaces());
dec_dot_h_file.close();
// Decoder .cc file.
dec_dot_cc_file << GenerateCcFileProlog(dec_dot_h_name,
instruction_set->namespaces());
dec_dot_cc_file << instruction_set->GenerateClassDefinitions(
dec_dot_h_name, encoding_type_name);
dec_dot_cc_file << GenerateNamespaceEpilog(instruction_set->namespaces());
dec_dot_cc_file.close();
// Enum files.
enum_h_file << GenerateSimpleHdrProlog(ToHeaderGuard(enum_h_name),
instruction_set->namespaces());
enum_cc_file << GenerateCcFileProlog(enum_h_name,
instruction_set->namespaces());
auto [h_output, cc_output] = instruction_set->GenerateEnums(enum_h_name);
enum_h_file << h_output;
enum_cc_file << cc_output;
enum_h_file << GenerateHdrFileEpilog(ToHeaderGuard(enum_h_name),
instruction_set->namespaces());
enum_cc_file << GenerateNamespaceEpilog(instruction_set->namespaces());
enum_h_file.close();
enum_cc_file.close();
// Encoder files
guard_name = ToHeaderGuard(enc_dot_h_name);
auto [enc_dot_h_prolog, enc_dot_cc_prolog] =
GenerateEncFilePrologs(enc_dot_h_name, guard_name, enum_h_name,
encoding_type_name, instruction_set->namespaces());
enc_dot_h_file << enc_dot_h_prolog;
enc_dot_cc_file << enc_dot_cc_prolog;
auto [h_enc, cc_enc] = instruction_set->GenerateEncClasses(
enc_dot_h_name, enum_h_name, encoding_type_name);
enc_dot_h_file << h_enc;
enc_dot_cc_file << cc_enc;
enc_dot_h_file << GenerateHdrFileEpilog(guard_name,
instruction_set->namespaces());
enc_dot_cc_file << GenerateNamespaceEpilog(
instruction_set->namespaces()); // Enum .h and .cc files.
enc_dot_h_file.close();
enc_dot_cc_file.close();
return absl::OkStatus();
}
void InstructionSetVisitor::PerformBundleReferenceChecks(
InstructionSet *instruction_set, Bundle *bundle) {
// Verify that all referenced bundles were declared.
for (const auto &bundle_name : bundle->bundle_names()) {
Bundle *bundle_ref = instruction_set->GetBundle(bundle_name);
// Perform the check recursively on the referenced bundles.
PerformBundleReferenceChecks(instruction_set, bundle_ref);
}
// Verify that all the slot uses were declared.
for (auto &[slot_name, instance_vec] : bundle->slot_uses()) {
Slot *slot = instruction_set->GetSlot(slot_name);
// Verify that the instance number of the slot falls within valid range.
for (auto &instance_number : instance_vec) {
if (instance_number >= slot->size()) {
auto *token = bundle->ctx() == nullptr ? nullptr : bundle->ctx()->start;
error_listener()->semanticError(
token,
absl::StrCat("Index ", instance_number, " out of range for slot ",
slot_name, "' referenced in bundle '", bundle->name(),
"'"));
continue;
}
}
if (slot->is_referenced()) continue;
if ((slot->default_instruction() == nullptr) ||
slot->default_instruction()->semfunc_code_string().empty()) {
error_listener()->semanticError(
slot->ctx()->start,
absl::StrCat("Slot '", slot->name(),
"' lacks a default semantic action"));
}
slot->set_is_referenced(true);
}
instruction_set->ComputeSlotAndBundleOrders();
}
void InstructionSetVisitor::VisitTopLevel(TopLevelCtx *ctx) {
auto declarations = ctx->declaration();
// Process disasm widths. Only the one in the top level file is used if there
// are additional ones in included files.
int count = 0;
DisasmWidthsCtx *disasm_ctx = nullptr;
for (auto *decl : declarations) {
context_file_map_[decl] = current_file_index_;
if (decl->disasm_widths() == nullptr) continue;
if (count > 0) {
error_listener()->semanticError(
decl->disasm_widths()->start,
absl::StrCat("Only one `disasm width` declaration allowed - "
"previous declaration on line: ",
disasm_ctx->start->getLine()));
}
disasm_ctx = decl->disasm_widths();
context_file_map_[disasm_ctx] = context_file_map_.at(decl);
VisitDisasmWidthsDecl(decl->disasm_widths());
count++;
}
// Parse, but don't process all the slots, bundles, isas and include files.
PreProcessDeclarations(declarations);
}
std::unique_ptr<InstructionSet> InstructionSetVisitor::ProcessTopLevel(
absl::string_view isa_name) {
// At this point we have the contexts for all isas, bundles, and slots.
// First make sure that the named isa has been defined.
auto isa_ptr = isa_decl_map_.find(isa_name);
if (isa_ptr == isa_decl_map_.end()) {
error_listener()->semanticError(
nullptr, absl::StrCat("No isa '", isa_name, "' declared"));
return nullptr;
}
// Visit the Isa.
auto isa = VisitIsaDeclaration(isa_ptr->second);
return isa;
}
void InstructionSetVisitor::PreProcessDeclarations(
const std::vector<DeclarationCtx *> &ctx_vec) {
std::vector<IncludeFileCtx *> include_files;
// Get handles to the slot, bundle and isa declarations.
// Create map from slot name to slot ctx.
for (auto *decl : ctx_vec) {
if (decl->slot_declaration() != nullptr) {
auto *slot_ctx = decl->slot_declaration();
context_file_map_.insert({slot_ctx, current_file_index_});
auto name = slot_ctx->slot_name->getText();
auto ptr = slot_decl_map_.find(name);
if (ptr != slot_decl_map_.end()) {
error_listener()->semanticError(
slot_ctx->start,
absl::StrCat("Slot '", name,
"' already declared - previous declaration on line: ",
slot_ctx->start->getLine()));
}
slot_decl_map_.emplace(name, slot_ctx);
}
// Create map from bundle name to bundle ctx.
if (decl->bundle_declaration() != nullptr) {
auto *bundle_ctx = decl->bundle_declaration();
context_file_map_.insert({bundle_ctx, current_file_index_});
auto name = bundle_ctx->bundle_name->getText();
auto ptr = bundle_decl_map_.find(name);
if (ptr != bundle_decl_map_.end()) {
error_listener()->semanticError(
bundle_ctx->start,
absl::StrCat("Bundle '", name,
"' already declared - previous declaration on line: ",
bundle_ctx->start->getLine()));
continue;
}
bundle_decl_map_.emplace(name, bundle_ctx);
}
// Create map from isa name to isa ctx.
if (decl->isa_declaration() != nullptr) {
auto *isa_ctx = decl->isa_declaration();
context_file_map_.insert({isa_ctx, current_file_index_});
auto name = isa_ctx->instruction_set_name->getText();
auto ptr = isa_decl_map_.find(name);
if (ptr != isa_decl_map_.end()) {
error_listener()->semanticError(
isa_ctx->start,
absl::StrCat("Isa '", name,
"' already declared - previous declaration on line: ",
isa_ctx->start->getLine()));
continue;
}
isa_decl_map_.emplace(name, isa_ctx);
}
// Process global include file specifications.
if (decl->include_file_list() != nullptr) {
for (auto *include_file : decl->include_file_list()->include_file()) {
// Insert the string - the call will always succeed, but the insertion
// does not happen if it already exists.
include_files_.insert(include_file->STRING_LITERAL()->getText());
}
}
// Process global constants.
if (decl->constant_def() != nullptr) {
context_file_map_.insert({decl->constant_def(), current_file_index_});
VisitConstantDef(decl->constant_def());
}
// Process .isa include file.
if (decl->include_file() != nullptr) {
include_files.push_back(decl->include_file());
}
}
// Process all include files - this adds to all isa, slot and bundle
// context maps, as well as all global variables, etc.
for (auto *include_file_ctx : include_files) {
VisitIncludeFile(include_file_ctx);
}
}
std::unique_ptr<InstructionSet> InstructionSetVisitor::VisitIsaDeclaration(
IsaDeclCtx *ctx) {
if (ctx == nullptr) return nullptr;
auto instruction_set =
std::make_unique<InstructionSet>(ctx->instruction_set_name->getText());
// An InstructionSet also acts as (has-a bundle) - it's the top level
// bundle.
instruction_set->set_bundle(
new Bundle(instruction_set->name(), instruction_set.get(), nullptr));
// Visit the namespace declaration, and the bundle and slot references that
// are part of the instruction_set declaration.
VisitNamespaceDecl(ctx->namespace_decl(), instruction_set.get());
context_file_map_[ctx->bundle_list()] = context_file_map_.at(ctx);
VisitBundleList(ctx->bundle_list(), instruction_set->bundle());
context_file_map_[ctx->slot_list()] = context_file_map_.at(ctx);
VisitSlotList(ctx->slot_list(), instruction_set->bundle());
return instruction_set;
}
void InstructionSetVisitor::VisitNamespaceDecl(NamespaceDeclCtx *ctx,
InstructionSet *isa) {
if (ctx == nullptr) return;
for (auto *namespace_name : ctx->namespace_ident()) {
isa->namespaces().push_back(namespace_name->getText());
}
}
void InstructionSetVisitor::VisitBundleList(BundleListCtx *ctx,
Bundle *bundle) {
if (ctx == nullptr) return;
// Append the list of named bundles referenced within the containing bundle.
auto bundle_specs_vec = ctx->bundle_spec();
for (auto bundle_spec : bundle_specs_vec) {
auto bundle_name = bundle_spec->IDENT()->getText();
auto iter = bundle_decl_map_.find(bundle_name);
// If the name hasn't been seen, flag an error.
if (iter == bundle_decl_map_.end()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], bundle_spec->start,
absl::StrCat("Reference to undefined bundle: '", bundle_name, "'"));
continue;
}
// If the bundle hasn't been processed yet, visit its declaration.
if (!bundle->instruction_set()->bundle_map().contains(bundle_name)) {
VisitBundleDeclaration(iter->second, bundle->instruction_set());
}
bundle->AppendBundleName(bundle_spec->IDENT()->getText());
}
}
void InstructionSetVisitor::VisitSlotList(SlotListCtx *ctx, Bundle *bundle) {
if (ctx == nullptr) return;
// Append the list of named slots referenced within the containing bundle.
auto slot_specs_vec = ctx->slot_spec();
for (auto slot_spec : slot_specs_vec) {
auto slot_name = slot_spec->IDENT()->getText();
auto iter = slot_decl_map_.find(slot_name);
// If the slot hasn't been seen, flag an error.
if (iter == slot_decl_map_.end()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], slot_spec->start,
absl::StrCat("Reference to undefined slot: '", slot_name, "'"));
continue;
}
// If the slot hasn't been processed yet, visit its declaration.
if (!bundle->instruction_set()->slot_map().contains(slot_name)) {
VisitSlotDeclaration(iter->second, bundle->instruction_set());
}
// First obtain the vector of instance indices specified before appending.
std::vector<int> instances = VisitArraySpec(slot_spec->array_spec());
bundle->AppendSlot(slot_name, instances);
}
}
std::vector<int> InstructionSetVisitor::VisitArraySpec(ArraySpecCtx *ctx) {
std::vector<int> instances;
// If there are not array specifications, return the empty vector.
if (ctx == nullptr) return instances;
auto range_spec_vec = ctx->range_spec();
for (auto range_spec : range_spec_vec) {
// The range spec is on the form of n, or m..n. Add the appropriate
// indices to the instances vector.
int range_start;
int range_end;
range_end = range_start = std::stoi(range_spec->range_start->getText());
if (range_spec->range_end != nullptr) {
range_end = std::stoi(range_spec->range_end->getText());
}
for (int instance = range_start; instance <= range_end; instance++) {
instances.push_back(instance);
}
}
return instances;
}
void InstructionSetVisitor::VisitConstantDef(ConstantDefCtx *ctx) {
if (ctx == nullptr) return;
std::string ident = ctx->ident()->getText();
std::string type = ctx->template_parameter_type()->getText();
context_file_map_.insert({ctx->expression(), context_file_map_.at(ctx)});
auto *expr = VisitExpression(ctx->expression(), nullptr, nullptr);
auto status = AddConstant(ident, type, expr);
if (!status.ok()) {
delete expr;
error_listener()->semanticError(ctx->ident()->start, status.message());
}
}
void InstructionSetVisitor::VisitIncludeFile(IncludeFileCtx *ctx) {
// The literal includes the double quotes.
std::string literal = ctx->STRING_LITERAL()->getText();
// Remove the double quotes from the literal and construct the full file
// name.
std::string file_name = literal.substr(1, literal.length() - 2);
// Check for recursive include.
for (auto const &name : include_file_stack_) {
if (name == file_name) {
error_listener()->semanticError(
ctx->start,
absl::StrCat(": ", "Recursive include of '", file_name, "'"));
return;
}
}
ParseIncludeFile(ctx, file_name, include_dir_vec_);
}
void InstructionSetVisitor::ParseIncludeFile(
antlr4::ParserRuleContext *ctx, const std::string &file_name,
const std::vector<std::string> &dirs) {
std::fstream include_file;
// Open include file.
include_file.open(file_name, std::fstream::in);
if (!include_file.is_open()) {
// Try each of the include file directories.
for (auto const &dir : dirs) {
std::string include_name = dir + "/" + file_name;
include_file.open(include_name, std::fstream::in);
if (include_file.is_open()) break;
}
if (!include_file.is_open()) {
error_listener()->semanticError(
ctx == nullptr ? nullptr : ctx->start,
absl::StrCat("Failed to open '", file_name, "'", " ", dirs.size()));
return;
}
}
std::string previous_file_name = error_listener()->file_name();
int previous_file_index_ = current_file_index_;
error_listener()->set_file_name(file_name);
file_names_.push_back(file_name);
current_file_index_ = file_names_.size() - 1;
// Create an antlr4 stream from the input stream.
auto *include_parser = new IsaAntlrParserWrapper(&include_file);
// We need to save the parser state so it's available for analysis after
// we are done with building the parse trees.
antlr_parser_wrappers_.push_back(include_parser);
// Add the error listener.
include_parser->parser()->removeErrorListeners();
include_parser->parser()->addErrorListener(error_listener());
// Start parsing at the include_top_level rule.
auto declaration_vec =
include_parser->parser()->include_top_level()->declaration();
include_file.close();
if (error_listener()->syntax_error_count() > 0) {
error_listener()->set_file_name(previous_file_name);
current_file_index_ = previous_file_index_;
return;
}
include_file_stack_.push_back(file_name);
PreProcessDeclarations(declaration_vec);
include_file_stack_.pop_back();
error_listener()->set_file_name(previous_file_name);
current_file_index_ = previous_file_index_;
}
void InstructionSetVisitor::VisitBundleDeclaration(
BundleDeclCtx *ctx, InstructionSet *instruction_set) {
if (ctx == nullptr) return;
Bundle *bundle =
new Bundle(ctx->bundle_name->getText(), instruction_set, ctx);
instruction_set->AddBundle(bundle);
context_file_map_[ctx->bundle_list()] = context_file_map_.at(ctx);
VisitBundleList(ctx->bundle_list(), bundle);
context_file_map_[ctx->slot_list()] = context_file_map_.at(ctx);
VisitSlotList(ctx->slot_list(), bundle);
}
void InstructionSetVisitor::VisitSlotDeclaration(
SlotDeclCtx *ctx, InstructionSet *instruction_set) {
bool is_templated = ctx->template_decl() != nullptr;
Slot *slot =
new Slot(ctx->slot_name->getText(), instruction_set, is_templated, ctx);
if (is_templated) {
for (auto const &param : ctx->template_decl()->template_parameter_decl()) {
auto status = slot->AddTemplateFormal(param->IDENT()->getText());
if (!status.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], param->start,
status.message());
}
}
}
// Set the base slot if it inherits.
if (ctx->base_item_list() != nullptr) {
// For each entry in the list of slots to derive from.
for (auto *base_item : ctx->base_item_list()->base_item()) {
std::string base_name = base_item->IDENT()->getText();
// If the base slot does has not been seen - undefined error.
auto slot_iter = slot_decl_map_.find(base_name);
if (slot_iter == slot_decl_map_.end()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], base_item->start,
absl::StrCat("Undefined base slot: ", base_name));
continue;
}
// If the slot hasn't been visited, visit it.
auto *base = instruction_set->GetSlot(base_name);
if (base == nullptr) {
VisitSlotDeclaration(slot_iter->second, instruction_set);
base = instruction_set->GetSlot(base_name);
}
// Now check if the base slot is templated or not, and if the template
// arguments are present or not.
auto *template_spec = base_item->template_spec();
if ((template_spec != nullptr) && !base->is_templated()) {
// Template arguments are present but the slot isn't templated.
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], base_item->start,
absl::StrCat("'", base_name, "' is not a templated slot"));
continue;
}
if ((template_spec == nullptr) && base->is_templated()) {
// The slot is templated, but no template arguments.
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], base_item->start,
absl::StrCat("Missing template arguments for slot '", base_name,
"'"));
continue;
}
if (template_spec != nullptr) {
// Check that the number of arguments match.
int arg_count = template_spec->expression().size();
int param_count = base->template_parameters().size();
if (arg_count != param_count) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], template_spec->start,
absl::StrCat("Wrong number of arguments: ", param_count,
" were expected, ", arg_count, " were provided"));
continue;
}
bool has_error = false;
// Build up the argument vector.
auto *arguments = new TemplateInstantiationArgs;
for (auto *template_arg : template_spec->expression()) {
context_file_map_.insert({template_arg, context_file_map_.at(ctx)});
auto *expr = VisitExpression(template_arg, slot, nullptr);
if (expr == nullptr) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], template_arg->start,
"Error in template expression");
has_error = true;
break;
}
arguments->push_back(expr);
}
if (has_error) {
for (auto *expr : *arguments) {
delete expr;
}
delete arguments;
continue;
}
auto result = slot->AddBase(base, arguments);
if (!result.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], base_item->start,
result.message());
}
} else {
// No template arguments.
auto result = slot->AddBase(base);
if (!result.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], base_item->start,
result.message());
}
}
}
}
// Set the size if it is replicated.
if (ctx->size_spec() != nullptr) {
int size = std::stoi(ctx->size_spec()->NUMBER()->getText(), nullptr, 0);
slot->set_size(size);
}
// Add the slot to the ISA.
instruction_set->AddSlot(slot);
for (auto *decl_ctx : ctx->const_and_default_decl()) {
context_file_map_[decl_ctx] = context_file_map_.at(ctx);
VisitConstAndDefaultDecls(decl_ctx, slot);
}
context_file_map_[ctx->opcode_list()] = context_file_map_.at(ctx);
VisitOpcodeList(ctx->opcode_list(), slot);
}
void InstructionSetVisitor::VisitDisasmWidthsDecl(DisasmWidthsCtx *ctx) {
for (auto *expr : ctx->expression()) {
context_file_map_.insert({expr, context_file_map_.at(ctx)});
auto *width_expr = VisitExpression(expr, nullptr, nullptr);
if ((width_expr == nullptr) || (!width_expr->IsConstant())) {
error_listener()->semanticError(expr->start,
"Expression must be constant");
continue;
}
disasm_field_widths_.push_back(width_expr);
}
}
void InstructionSetVisitor::VisitConstAndDefaultDecls(ConstAndDefaultCtx *ctx,
Slot *slot) {
if (ctx == nullptr) return;
// A constant declaration.
auto *const_def = ctx->constant_def();
if (const_def != nullptr) { // Constant declaration.
std::string ident = const_def->ident()->getText();
std::string type = const_def->template_parameter_type()->getText();
context_file_map_.insert(
{const_def->expression(), context_file_map_.at(ctx)});
auto *expr = VisitExpression(const_def->expression(), slot, nullptr);
if (expr == nullptr) {
delete expr;
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
const_def->expression()->start,
"Error in expression");
return;
}
auto status = slot->AddConstant(ident, type, expr);
if (!status.ok()) {
delete expr;
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
const_def->ident()->start,
status.message());
}
return;
}
if (ctx->SIZE() != nullptr) { // Default size.
int value = std::stoi(ctx->NUMBER()->getText(), nullptr, 0);
slot->set_default_instruction_size(value);
return;
}
if (ctx->LATENCY() != nullptr) { // Default latency.
context_file_map_.insert({ctx->expression(), context_file_map_.at(ctx)});
auto *expr = VisitExpression(ctx->expression(), slot, nullptr);
if (expr == nullptr) {
delete expr;
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
ctx->expression()->start,
"Error in expression");
return;
}
slot->set_default_latency(expr);
return;
}
if (ctx->ATTRIBUTES() != nullptr) { // Default attributes.
context_file_map_.insert(
{ctx->instruction_attribute_list(), context_file_map_.at(ctx)});
VisitInstructionAttributeList(ctx->instruction_attribute_list(), slot,
nullptr);
return;
}
// Add any include files to our set of includes.
if (ctx->include_file_list() != nullptr) {
for (auto *include_file : ctx->include_file_list()->include_file()) {
// Insert the string - the call will always succeed, but the insertion
// does not happen if it already exists.
include_files_.insert(include_file->STRING_LITERAL()->getText());
}
}
if (ctx->OPCODE() != nullptr) { // Default opcode.
// Process the "default" instruction, which is used to specify disassembly
// and semantic function for when no valid opcode is found during decode.
if (slot->default_instruction() != nullptr) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
"Multiple definitions of 'default' opcode");
return;
}
auto *default_instruction = new Instruction(
slot->instruction_set()->opcode_factory()->CreateDefaultOpcode(), slot);
bool has_disasm = false;
bool has_semfunc = false;
for (auto *attribute : ctx->opcode_attribute_list()->opcode_attribute()) {
// Disasm spec.
if (attribute->disasm_spec() != nullptr) {
if (has_disasm) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], attribute->start,
"Duplicate disasm declaration");
continue;
}
has_disasm = true;
for (auto *format_str : attribute->disasm_spec()->STRING_LITERAL()) {
std::string format = format_str->getText();
// Trim the double quotes.
format.erase(format.size() - 1, 1);
format.erase(0, 1);
auto status = ParseDisasmFormat(format, default_instruction);
if (!status.ok()) {
error_listener()->semanticError(attribute->disasm_spec()->start,
status.message());
}
}
continue;
}
// Semfunc spec.
// If a semfunc has already been declared, signal an error.
if (has_semfunc) {
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
attribute->start,
"Duplicate semfunc declaration");
continue;
}
has_semfunc = true;
auto semfunc_code = attribute->semfunc_spec()->STRING_LITERAL();
// Only one semfunc specification (no child instructions) for default
// opcode.
if (semfunc_code.size() > 1) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
"Only one semfunc specification per default opcode");
continue;
}
std::string string_literal = semfunc_code[0]->getText();
// Strip double quotes.
std::string code_string =
string_literal.substr(1, string_literal.length() - 2);
default_instruction->set_semfunc_code_string(code_string);
}
if (has_semfunc) {
slot->set_default_instruction(default_instruction);
} else {
delete default_instruction;
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
"Default opcode lacks mandatory semfunc specification");
}
}
if (ctx->resource_details() != nullptr) {
std::string ident = ctx->ident()->getText();
if (slot->resource_spec_map().contains(ident)) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->ident()->start,
absl::StrCat("Resources '", ident, "': duplicate definition"));
return;
}
// Save the context. It will be re-visited at each point of use.
slot->resource_spec_map().insert(
std::make_pair(ident, ctx->resource_details()));
}
}
// Visit the template argument recursively to create an expression tree that
// can be evaluated later. No need to coalesce constant expression trees, the
// savings aren't that great.
TemplateExpression *InstructionSetVisitor::VisitExpression(ExpressionCtx *ctx,
Slot *slot,
Instruction *inst) {
if (ctx == nullptr) return nullptr;
if (ctx->negop() != nullptr) {
context_file_map_.insert({ctx->expr, context_file_map_.at(ctx)});
TemplateExpression *expr = VisitExpression(ctx->expr, slot, inst);
if (expr == nullptr) return nullptr;
return new TemplateNegate(expr);
}
if (ctx->mulop() != nullptr) {
std::string op = ctx->mulop()->getText();
context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)});
TemplateExpression *lhs = VisitExpression(ctx->lhs, slot, inst);
if (lhs == nullptr) return nullptr;
context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)});
TemplateExpression *rhs = VisitExpression(ctx->rhs, slot, inst);
if (rhs == nullptr) {
delete lhs;
return nullptr;
}
if (op == "*") return new TemplateMultiply(lhs, rhs);
return new TemplateDivide(lhs, rhs);
}
if (ctx->addop() != nullptr) {
std::string op = ctx->addop()->getText();
context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)});
TemplateExpression *lhs = VisitExpression(ctx->lhs, slot, inst);
if (lhs == nullptr) return nullptr;
context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)});
TemplateExpression *rhs = VisitExpression(ctx->rhs, slot, inst);
if (rhs == nullptr) {
delete lhs;
return nullptr;
}
if (op == "+") return new TemplateAdd(lhs, rhs);
return new TemplateSubtract(lhs, rhs);
}
if (ctx->func != nullptr) {
std::string function = ctx->func->getText();
auto iter = template_function_evaluators_.find(function);
if (iter == template_function_evaluators_.end()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
absl::StrCat("No function '", function, "' supported"));
return nullptr;
}
auto evaluator = iter->second;
if (ctx->expression().size() != evaluator.arity) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
absl::StrCat("Function '", function, "' takes ", evaluator.arity,
" parameters, but ", ctx->expression().size(),
" were given"));
}
auto *args = new TemplateInstantiationArgs;
bool has_error = false;
for (auto *expr_ctx : ctx->expression()) {
context_file_map_.insert({expr_ctx, context_file_map_.at(ctx)});
auto *expr = VisitExpression(expr_ctx, slot, inst);
if (expr == nullptr) {
has_error = true;
break;
}
args->push_back(expr);
}
if (has_error) {
for (auto *expr : *args) {
delete expr;
}
delete args;
return nullptr;
}
return new TemplateFunction(evaluator.function, args);
}
if (ctx->paren_expr != nullptr) {
context_file_map_.insert({ctx->paren_expr, context_file_map_.at(ctx)});
return VisitExpression(ctx->paren_expr, slot, inst);
}
if (ctx->NUMBER() != nullptr) {
auto *expr =
new TemplateConstant(std::stoi(ctx->NUMBER()->getText(), nullptr, 0));
return expr;
}
if (ctx->IDENT() != nullptr) {
std::string ident = ctx->IDENT()->getText();
// Four possibilities. A global constant, a slot local constant, a
// template formal, or a reference to a destination operand.
if (slot != nullptr) {
TemplateFormal *param = slot->GetTemplateFormal(ident);
if (param != nullptr) return new TemplateParam(param);
// Check if it's a slot const expression.
auto *expr = slot->GetConstExpression(ident);
if (expr != nullptr) return expr->DeepCopy();
}
// It should be an opcode operand term, which means it should be a
// destination operand with a latency. That is the value/expression that
// is needed here.
if (inst != nullptr) {
auto *op = inst->GetDestOp(ident);
if (op == nullptr) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
absl::StrCat("'", ident,
"' is not a valid destination operand for opcode '",
inst->opcode()->name(), "'"));
return nullptr;
}
auto *expr = op->expression();
if (expr != nullptr) return expr->DeepCopy();
// expr is null, this means that the destination operand has a decode
// time computed latency. This is a special case that will be addressed
// later. For now, signal an unsupported error.
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
ctx->start,
"Decode time evaluation of latency "
"expression not supported for resources");
return nullptr;
}
auto *expr = GetConstExpression(ident);
if (expr != nullptr) return expr->DeepCopy();
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
absl::StrCat("Unable to evaluate expression: ", "'", ctx->getText(),
"'"));
}
return nullptr;
}
DestinationOperand *InstructionSetVisitor::FindDestinationOpInExpression(
ExpressionCtx *ctx, const Slot *slot, const Instruction *inst) {
if (ctx == nullptr) return nullptr;
if (ctx->negop() != nullptr) {
context_file_map_.insert({ctx->expr, context_file_map_.at(ctx)});
return FindDestinationOpInExpression(ctx->expr, slot, inst);
}
if ((ctx->mulop() != nullptr) || (ctx->addop() != nullptr)) {
context_file_map_.insert({ctx->lhs, context_file_map_.at(ctx)});
context_file_map_.insert({ctx->rhs, context_file_map_.at(ctx)});
auto *lhs = FindDestinationOpInExpression(ctx->lhs, slot, inst);
auto *rhs = FindDestinationOpInExpression(ctx->rhs, slot, inst);
if (lhs == nullptr) return rhs;
if (rhs == nullptr) return lhs;
if (lhs == rhs) return lhs;
error_listener()->semanticError(
ctx->start,
"Resource reference can only reference a single "
"destination operand");
return nullptr;
}
if (ctx->paren_expr != nullptr) {
context_file_map_.insert({ctx->paren_expr, context_file_map_.at(ctx)});
return FindDestinationOpInExpression(ctx->paren_expr, slot, inst);
}
if (ctx->NUMBER() != nullptr) {
return nullptr;
}
if (ctx->func != nullptr) {
DestinationOperand *dest_op = nullptr;
DestinationOperand *tmp_op;
for (auto *expr_ctx : ctx->expression()) {
context_file_map_.insert({expr_ctx, context_file_map_.at(ctx)});
tmp_op = FindDestinationOpInExpression(expr_ctx, slot, inst);
if (dest_op == nullptr) {
dest_op = tmp_op;
continue;
}
if (tmp_op != nullptr) {
if (dest_op != tmp_op) {
error_listener()->semanticError(
ctx->start,
"Resource reference can only reference a single "
"destination operand");
}
}
}
return dest_op;
}
std::string ident = ctx->IDENT()->getText();
// It is either a slot local constant, a template formal, or a reference
// to a destination operand.
TemplateFormal *param = slot->GetTemplateFormal(ident);
if (param != nullptr) return nullptr;
// Check if it's a slot const expression.
auto *expr = slot->GetConstExpression(ident);
if (expr != nullptr) return nullptr;
// It should be an opcode operand term.
return inst->GetDestOp(ident);
}
void InstructionSetVisitor::VisitOpcodeList(OpcodeListCtx *ctx, Slot *slot) {
absl::flat_hash_set<std::string> deleted_ops_set;
absl::flat_hash_set<OpcodeSpecCtx *> overridden_ops_set;
std::vector<Instruction *> instruction_vec;
if (ctx != nullptr) {
ProcessOpcodeList(ctx, slot, instruction_vec, deleted_ops_set,
overridden_ops_set);
}
// For all base slots, and all opcodes that aren't excluded, add the opcodes
// to the current slot. When adding the instruction, pass in any template
// instantiation arguments to the base slot so that any expressions for
// destination operand latencies can be evaluated.
for (auto const &base_slot : slot->base_slots()) {
if (base_slot.base->min_instruction_size() < slot->min_instruction_size()) {
slot->set_min_instruction_size(base_slot.base->min_instruction_size());
}
// Copy over the instructions that were not deleted.
for (auto &[unused, inst_ptr] : base_slot.base->instruction_map()) {
if (!deleted_ops_set.contains(inst_ptr->opcode()->name())) {
absl::Status status =
slot->AppendInheritedInstruction(inst_ptr, base_slot.arguments);
if (!status.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(ctx)], ctx->start,
status.message());
}
}
}
// Perform the overrides.
PerformOpcodeOverrides(overridden_ops_set, slot);
}
// Add the declared opcodes.
for (auto *inst : instruction_vec) {
absl::Status status = slot->AppendInstruction(inst);
if (!status.ok()) {
error_listener()->semanticError(file_names_[context_file_map_.at(ctx)],
ctx->start, status.message());
}
}
}
void InstructionSetVisitor::PerformOpcodeOverrides(
absl::flat_hash_set<OpcodeSpecCtx *> overridden_ops_set, Slot *slot) {
for (auto *override_ctx : overridden_ops_set) {
std::string name = override_ctx->name->getText();
auto *inst = slot->instruction_map().at(name);
VisitOpcodeAttributes(override_ctx->opcode_attribute_list(), inst, slot);
}
}
void InstructionSetVisitor::VisitOpcodeAttributes(OpcodeAttributeListCtx *ctx,
Instruction *inst,
Slot *slot) {
if (ctx == nullptr) return;
// These flags are used to detect multiple instances of each attribute.
bool has_disasm = false;
bool has_semfunc = false;
bool has_resources = false;
bool has_attributes = false;
// Visit the opcode attributes.
for (auto *attribute_ctx : ctx->opcode_attribute()) {
// Process any disassembly specifications.
if (attribute_ctx->disasm_spec() != nullptr) {
// In case of override, need to clear any disasm info in instruction.
inst->ClearDisasmFormat();
// Signal error if there is more than one disassembly spec.
if (has_disasm) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
attribute_ctx->start, "Multiple disasm specifications");
continue;
}
has_disasm = true;
for (auto *disasm_fmt : attribute_ctx->disasm_spec()->STRING_LITERAL()) {
std::string format = disasm_fmt->getText();
// Trim the double quotes.
format.erase(format.size() - 1, 1);
format.erase(0, 1);
auto status = ParseDisasmFormat(format, inst);
if (!status.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
attribute_ctx->disasm_spec()->start, status.message());
has_disasm = false;
break;
}
}
continue;
}
// Process the semantic function specification.
if (attribute_ctx->semfunc_spec() != nullptr) {
// In case of override, need to clear the semantic function string.
inst->ClearSemfuncCodeString();
// Signal error if there is more than one semantic function spec.
if (has_semfunc) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
attribute_ctx->start, "Multiple semfunc specifications");
continue;
}
has_semfunc = true;
VisitSemfuncSpec(attribute_ctx->semfunc_spec(), inst);
continue;
}
// Process resource specification.
if (attribute_ctx->resource_spec() != nullptr) {
// In case of override, need to clear the resource specifications.
inst->ClearResourceSpecs();
// Signal error if there is more than one resource specification.
if (has_resources) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
attribute_ctx->start, "Multiple resource specifications");
continue;
}
has_resources = true;
VisitResourceDetails(attribute_ctx->resource_spec()->resource_details(),
inst, slot);
continue;
}
// Process instruction attribute specification.
if (attribute_ctx->instruction_attribute_spec() != nullptr) {
// In case of override, need to clear the attribute specification.
inst->ClearAttributeSpecs();
// Signal error if there is more than one attribute specification.
if (has_attributes) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
attribute_ctx->start, "Multiple attribute specifications");
continue;
}
has_attributes = true;
auto attr_list_ctx = attribute_ctx->instruction_attribute_spec()
->instruction_attribute_list();
VisitInstructionAttributeList(attr_list_ctx, slot, inst);
continue;
}
// Unknown attribute type.
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], attribute_ctx->start,
"Unknown attribute type");
}
}
void InstructionSetVisitor::VisitInstructionAttributeList(
InstructionAttributeListCtx *ctx, Slot *slot, Instruction *inst) {
absl::flat_hash_map<std::string, TemplateExpression *> attributes;
for (auto *attribute : ctx->instruction_attribute()) {
std::string name = attribute->IDENT()->getText();
if (attributes.find(name) != attributes.end()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], attribute->start,
absl::StrCat("Duplicate attribute name '", name, "' in list"));
continue;
}
InstructionSet::AddAttributeName(name);
if (attribute->expression() != nullptr) {
context_file_map_.insert(
{attribute->expression(), context_file_map_.at(slot->ctx())});
auto *expr = VisitExpression(attribute->expression(), slot, inst);
attributes.emplace(name, expr);
continue;
}
attributes.emplace(name, new TemplateConstant(1));
}
// Are we parsing attributes for an instruction?
if (inst != nullptr) {
for (auto *child = inst; child != nullptr; child = child->child()) {
for (auto &[name, expr] : attributes) {
child->AddInstructionAttribute(name, expr);
}
}
// Ownership of expr objects transferred to opcode.
attributes.clear();
return;
}
// Attributes are default attributes for the current slot.
for (auto &[name, expr] : attributes) {
slot->AddInstructionAttribute(name, expr);
}
attributes.clear();
}
void InstructionSetVisitor::VisitSemfuncSpec(SemfuncSpecCtx *semfunc_spec,
Instruction *inst) {
auto *child = inst;
// Parse each string in the list of semantic function specifications. There
// should be one the opcode and one for each child opcode.
for (auto *sem_func : semfunc_spec->STRING_LITERAL()) {
if (child == nullptr) {
error_listener()->semanticWarning(
file_names_[context_file_map_.at(inst->slot()->ctx())],
sem_func->getSymbol(), "Ignoring extra semfunc spec");
break;
}
std::string literal = sem_func->getText();
std::string code_string = literal.substr(1, literal.length() - 2);
child->set_semfunc_code_string(code_string);
child = child->child();
}
// Are there fewer specifier strings than child instructions?
if (child != nullptr) {
error_listener()->semanticError(
file_names_[context_file_map_.at(inst->slot()->ctx())],
semfunc_spec->start,
absl::StrCat("Fewer semfunc specifiers than expected for opcode '",
inst->opcode()->name(), "'"));
}
}
void InstructionSetVisitor::VisitResourceDetails(ResourceDetailsCtx *ctx,
Instruction *inst,
Slot *slot) {
if (ctx->ident() != nullptr) {
// This is a reference to a resource spec defined earlier.
std::string name = ctx->ident()->getText();
auto iter = slot->resource_spec_map().find(name);
if (iter == slot->resource_spec_map().end()) {
// This should never happen.
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], ctx->start,
absl::StrCat("Internal error: Undefined resources name: '", name,
"'"));
return;
}
ctx = iter->second;
}
ResourceSpec spec;
VisitResourceDetailsLists(ctx, slot, inst, &spec);
for (auto *use : spec.use_vec) {
inst->AppendResourceUse(use);
}
for (auto *acquire : spec.acquire_vec) {
inst->AppendResourceAcquire(acquire);
}
}
std::optional<ResourceReference *>
InstructionSetVisitor::ProcessResourceReference(
Slot *slot, Instruction *inst, ResourceItemCtx *resource_item) {
// Empty optional object.
std::optional<ResourceReference *> return_value;
auto *factory = slot->instruction_set()->resource_factory();
DestinationOperand *dest_op = nullptr;
// Extract the text from the resource reference.
std::string ident_text;
bool is_array;
if (resource_item->name != nullptr) {
ident_text = resource_item->name->getText();
is_array = false;
} else {
// Prepend the name with [] so that it doesn't conflict with a non-array
// resource of the same name.
ident_text = resource_item->array_name->getText();
is_array = true;
}
dest_op = inst->GetDestOp(ident_text);
auto *resource = factory->GetOrInsertResource(ident_text);
resource->set_is_array(is_array);
// Compute begin and end values.
TemplateExpression *begin_expr;
TemplateExpression *end_expr;
DestinationOperand *tmp_op;
if (resource_item->begin_cycle == nullptr) {
begin_expr = new TemplateConstant(0);
} else {
context_file_map_.insert(
{resource_item->begin_cycle, context_file_map_.at(slot->ctx())});
tmp_op =
FindDestinationOpInExpression(resource_item->begin_cycle, slot, inst);
if (tmp_op != nullptr) {
if (dest_op == nullptr) {
dest_op = tmp_op;
} else if (dest_op != tmp_op) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
resource_item->start,
"Resource reference can only reference a single "
"destination operand");
return return_value;
}
}
context_file_map_.insert(
{resource_item->begin_cycle, context_file_map_.at(slot->ctx())});
begin_expr = VisitExpression(resource_item->begin_cycle, slot, inst);
}
if (resource_item->end_cycle == nullptr) {
// If there is no end_cycle specified, then it inherits from the dest_op
// if available.
// TODO(torerik): Handle case where the dest latency is '*'.
if ((dest_op != nullptr) && (dest_op->expression() != nullptr)) {
end_expr = dest_op->expression()->DeepCopy();
} else {
end_expr = new TemplateConstant(0);
}
} else {
context_file_map_.insert(
{resource_item->end_cycle, context_file_map_.at(slot->ctx())});
tmp_op =
FindDestinationOpInExpression(resource_item->end_cycle, slot, inst);
if (tmp_op != nullptr) {
if (dest_op == nullptr) {
dest_op = tmp_op;
} else if (dest_op != tmp_op) {
error_listener()->semanticError(
resource_item->start,
"Resource reference can only reference a single "
"destination operand");
delete begin_expr;
return return_value;
}
}
context_file_map_.insert(
{resource_item->end_cycle, context_file_map_.at(slot->ctx())});
end_expr = VisitExpression(resource_item->end_cycle, slot, inst);
}
auto *ref =
new ResourceReference(resource, is_array, dest_op, begin_expr, end_expr);
return std::optional<ResourceReference *>(ref);
}
void InstructionSetVisitor::VisitResourceDetailsLists(ResourceDetailsCtx *ctx,
Slot *slot,
Instruction *inst,
ResourceSpec *spec) {
if (ctx == nullptr) return;
if ((ctx->use_list == nullptr) && (ctx->acquire_list == nullptr) &&
(ctx->hold_list == nullptr))
return;
// The resource details consists of three lists: use, acquire, and
// hold. The "use" list specifies the resources that have to only be
// available at instruction dispatch time. The "acquire" list specifies the
// list of resources that the instruction must be available to acquire, and
// hold for a certain set of cycles. The hold list specifies the list of
// resources that will be marked held (regardless of their status) for the
// set of cycles specified.
// Use list.
auto *use_list = ctx->use_list;
if (use_list != nullptr) {
for (auto *resource_item : use_list->resource_item()) {
auto ref_optional = ProcessResourceReference(slot, inst, resource_item);
if (!ref_optional) continue;
spec->use_vec.push_back(ref_optional.value());
}
}
// Reserve list.
auto *acquire_list = ctx->acquire_list;
if (acquire_list != nullptr) {
for (auto *resource_item : acquire_list->resource_item()) {
auto ref_optional = ProcessResourceReference(slot, inst, resource_item);
if (!ref_optional) continue;
// Only add to use_vec if it isn't already there.
bool found = false;
for (auto *ref : spec->use_vec) {
if (ref->resource->name() == ref_optional.value()->resource->name()) {
found = true;
break;
}
}
if (!found) {
spec->use_vec.push_back(new ResourceReference(*ref_optional.value()));
}
spec->acquire_vec.push_back(ref_optional.value());
}
}
// Hold list.
auto *hold_list = ctx->hold_list;
if (hold_list != nullptr) {
for (auto *resource_item : hold_list->resource_item()) {
auto ref_optional = ProcessResourceReference(slot, inst, resource_item);
if (!ref_optional) continue;
spec->acquire_vec.push_back(ref_optional.value());
}
}
}
void InstructionSetVisitor::ProcessOpcodeList(
OpcodeListCtx *ctx, Slot *slot, std::vector<Instruction *> &instruction_vec,
absl::flat_hash_set<std::string> &deleted_ops_set,
absl::flat_hash_set<OpcodeSpecCtx *> &overridden_ops_set) {
// Obtain the list of opcodes specifications.
auto opcode_spec = ctx->opcode_spec();
for (auto *opcode_ctx : opcode_spec) {
ProcessOpcodeSpec(opcode_ctx, slot, instruction_vec, deleted_ops_set,
overridden_ops_set);
}
}
void InstructionSetVisitor::ProcessOpcodeSpec(
OpcodeSpecCtx *opcode_ctx, Slot *slot,
std::vector<Instruction *> &instruction_vec,
absl::flat_hash_set<std::string> &deleted_ops_set,
absl::flat_hash_set<OpcodeSpecCtx *> &overridden_ops_set) {
auto *opcode_factory = slot->instruction_set()->opcode_factory();
if (opcode_ctx->generate != nullptr) {
auto status = ProcessOpcodeGenerator(opcode_ctx, slot, instruction_vec,
deleted_ops_set, overridden_ops_set);
if (!status.ok()) {
error_listener()->semanticError(opcode_ctx->name, status.message());
}
return;
}
// Process the regular opcode specification.
std::string opcode_name = opcode_ctx->name->getText();
// Check to see if this opcode is deleted, meaning it should not be
// inherited from a base slot.
if (opcode_ctx->deleted != nullptr) {
// If there is no base slot, this is an error.
if (slot->base_slots().empty()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted,
absl::StrCat("Invalid deleted opcode '", opcode_name, "', slot '",
slot->name(), "' does not inherit from a base slot"));
return;
}
bool found = false;
// Check to see if one of the base slots has this opcode.
for (auto const &base_slot : slot->base_slots()) {
found |= base_slot.base->HasInstruction(opcode_name);
if (found) break;
}
// If the opcode was not defined in any of the base slots, it is an error.
if (!found) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted,
absl::StrCat("Base slot does not define or inherit opcode '",
opcode_name, "'"));
return;
}
deleted_ops_set.insert(opcode_name);
return;
}
// Check to see if this opcode is overridden, this means that some of the
// "attributes" (semantic function, disasm, etc.) are changed.
if (opcode_ctx->overridden != nullptr) {
int found = 0;
for (auto const &base_slot : slot->base_slots()) {
found += base_slot.base->HasInstruction(opcode_name);
}
// Check that the opcode is indeed inherited from one base class only.
// Multiple inheritance is not supported.
if (found == 0) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted,
absl::StrCat("Base slot does not define or inherit opcode '",
opcode_name, "'"));
return;
} else if (found > 1) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->deleted,
absl::StrCat("Multiple inheritance of opcodes is not supported: ",
opcode_name));
return;
}
overridden_ops_set.insert(opcode_ctx);
return;
}
// This is a new opcode, so let's create it. Signal failure if error.
absl::StatusOr<Opcode *> result = opcode_factory->CreateOpcode(opcode_name);
if (!result.ok()) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], opcode_ctx->name,
result.status().message());
return;
}
Opcode *top = result.value();
auto inst = new Instruction(top, slot);
slot->instruction_set()->AddInstruction(inst);
// Get the size of the instruction if specified, otherwise use default size.
if (opcode_ctx->size_spec() != nullptr) {
int size =
std::stoi(opcode_ctx->size_spec()->NUMBER()->getText(), nullptr, 0);
top->set_instruction_size(size);
} else {
top->set_instruction_size(slot->default_instruction_size());
}
if (top->instruction_size() < slot->min_instruction_size()) {
slot->set_min_instruction_size(top->instruction_size());
}
int op_spec_number = 0;
auto op_spec = opcode_ctx->operand_spec();
// Process the top instruction.
for (auto &[name, expr] : slot->attribute_map()) {
inst->AddInstructionAttribute(name, expr->DeepCopy());
}
// Visit the opcode specification of the top instruction.
if (op_spec->opcode_operands() != nullptr) {
VisitOpcodeOperands(op_spec->opcode_operands(), op_spec_number, inst, inst,
slot);
} else {
VisitOpcodeOperands(op_spec->opcode_operands_list()->opcode_operands()[0],
op_spec_number, inst, inst, slot);
}
op_spec_number++;
// If there are child instructions process them.
if (opcode_ctx->operand_spec()->opcode_operands_list() != nullptr) {
Opcode *parent = top;
auto opcode_operands = op_spec->opcode_operands_list()->opcode_operands();
Instruction *child_inst = nullptr;
// Process child instructions.
for (size_t i = 1; i < opcode_operands.size(); ++i) {
// Create child opcode.
auto *op = opcode_factory->CreateChildOpcode(parent);
// Create child instruction.
child_inst = new Instruction(op, slot);
inst->AppendChild(child_inst);
// Add default attributes.
for (auto &[name, expr] : slot->attribute_map()) {
child_inst->AddInstructionAttribute(name, expr->DeepCopy());
}
VisitOpcodeOperands(opcode_operands[i], op_spec_number, inst, child_inst,
slot);
op_spec_number++;
}
}
instruction_vec.push_back(inst);
VisitOpcodeAttributes(opcode_ctx->opcode_attribute_list(), inst, slot);
}
void InstructionSetVisitor::VisitOpcodeOperands(OpcodeOperandsCtx *ctx,
int op_spec_number,
Instruction *parent,
Instruction *child,
Slot *slot) {
if (ctx == nullptr) return;
if (ctx->pred != nullptr) {
std::string name = ctx->pred->getText();
child->opcode()->set_predicate_op_name(name);
parent->opcode()->op_locator_map().insert(
std::make_pair(name, OperandLocator(op_spec_number, 'p', 0)));
}
if (ctx->source != nullptr) {
int instance = 0;
for (auto *source_op : ctx->source->source_operand()) {
std::string name;
bool is_array;
if (source_op->source != nullptr) {
name = source_op->source->getText();
is_array = false;
} else {
name = source_op->array_source->getText();
is_array = true;
}
child->opcode()->AppendSourceOp(name, is_array);
parent->opcode()->op_locator_map().insert(std::make_pair(
name,
OperandLocator(op_spec_number, is_array ? 't' : 's', instance)));
instance++;
}
}
if (ctx->dest_list() != nullptr) {
int instance = 0;
for (auto *dest_op : ctx->dest_list()->dest_operand()) {
std::string ident;
bool is_array;
if (dest_op->dest != nullptr) {
ident = dest_op->dest->getText();
is_array = false;
} else {
ident = dest_op->array_dest->getText();
is_array = true;
}
// The latency of the destination operand is either specified by an
// expression, by '*' (wildcard), or omitted, in which case it
// defaults to 1.
if (dest_op->expression() != nullptr) {
context_file_map_.insert(
{dest_op->expression(), context_file_map_.at(slot->ctx())});
child->opcode()->AppendDestOp(
ident, is_array,
VisitExpression(dest_op->expression(), slot, child));
} else if (dest_op->wildcard != nullptr) {
child->opcode()->AppendDestOp(ident, is_array);
} else if (slot->default_latency() != nullptr) {
child->opcode()->AppendDestOp(ident, is_array,
slot->default_latency()->DeepCopy());
} else {
child->opcode()->AppendDestOp(ident, is_array, new TemplateConstant(1));
}
parent->opcode()->op_locator_map().insert(std::make_pair(
ident,
OperandLocator(op_spec_number, is_array ? 'e' : 'd', instance)));
instance++;
}
}
}
absl::Status InstructionSetVisitor::ProcessOpcodeGenerator(
OpcodeSpecCtx *ctx, Slot *slot, std::vector<Instruction *> &instruction_vec,
absl::flat_hash_set<std::string> &deleted_ops_set,
absl::flat_hash_set<OpcodeSpecCtx *> &overridden_ops_set) {
if (ctx == nullptr) return absl::InternalError("OpcodeSpecCtx is null");
absl::flat_hash_set<std::string> range_variable_names;
std::vector<RangeAssignmentInfo *> range_info_vec;
// Process range assignment lists. The range assignment is either a single
// value or a structured binding assignment. If it's a binding assignment we
// need to make sure each tuple has the same number of values as there are
// idents to assign them to.
for (auto *assign_ctx : ctx->range_assignment()) {
auto *range_info = new RangeAssignmentInfo();
range_info_vec.push_back(range_info);
for (auto *ident_ctx : assign_ctx->IDENT()) {
std::string name = ident_ctx->getText();
if (range_variable_names.contains(name)) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())], assign_ctx->start,
absl::StrCat("Duplicate binding variable name '", name, "'"));
continue;
}
range_variable_names.insert(name);
range_info->range_names.push_back(ident_ctx->getText());
range_info->range_values.push_back({});
range_info->range_regexes.emplace_back(
absl::StrCat("\\$\\(", name, "\\)"));
// Verify that the range variable is used in the string.
if (!RE2::PartialMatch(ctx->generator_opcode_spec_list()->getText(),
range_info->range_regexes.back())) {
error_listener()->semanticWarning(
file_names_[context_file_map_.at(slot->ctx())], assign_ctx->start,
absl::StrCat("Unreferenced binding variable '", name, "'"));
}
}
// See if it's a list of simple values.
if (!assign_ctx->gen_value().empty()) {
for (auto *gen_value_ctx : assign_ctx->gen_value()) {
if (gen_value_ctx->simple != nullptr) {
range_info->range_values[0].push_back(
gen_value_ctx->simple->getText());
} else {
// Strip off double quotes.
std::string value = gen_value_ctx->string->getText();
range_info->range_values[0].push_back(
value.substr(1, value.size() - 2));
}
}
continue;
}
// It's a list of tuples with a structured binding assignment.
for (auto *tuple_ctx : assign_ctx->tuple()) {
if (tuple_ctx->gen_value().size() != range_info->range_names.size()) {
for (auto *info : range_info_vec) delete info;
return absl::InternalError(
"Number of values differs from number of identifiers");
}
for (int i = 0; i < tuple_ctx->gen_value().size(); ++i) {
if (tuple_ctx->gen_value(i)->simple != nullptr) {
range_info->range_values[i].push_back(
tuple_ctx->gen_value(i)->simple->getText());
} else {
// Strip off double quotes.
std::string value = tuple_ctx->gen_value(i)->string->getText();
range_info->range_values[i].push_back(
value.substr(1, value.size() - 2));
}
}
}
}
// Check that all binding variable references are valid.
std::string input_text = ctx->generator_opcode_spec_list()->getText();
std::string::size_type start_pos = 0;
auto pos = input_text.find_first_of('$', start_pos);
while (pos != std::string::npos) {
// Skip past the '$('
start_pos = pos + 2;
auto end_pos = input_text.find_first_of(')', pos);
// Extract the ident.
auto ident = input_text.substr(start_pos, end_pos - start_pos);
if (!range_variable_names.contains(ident)) {
error_listener()->semanticError(
file_names_[context_file_map_.at(slot->ctx())],
ctx->generator_opcode_spec_list()->start,
absl::StrCat("Undefined binding variable '", ident, "'"));
}
start_pos = end_pos;
pos = input_text.find_first_of('$', start_pos);
}
if (error_listener()->HasError()) {
for (auto *info : range_info_vec) delete info;
return absl::InternalError("Found undefined binding variable name(s)");
}
// Now we need to iterate over the range_info instances and substitution
// ranges. This will produce new text that will be parsed and processed.
std::string generated_text =
GenerateOpcodeSpec(range_info_vec, 0, input_text);
// Parse and process the generated text.
auto *parser = new IsaAntlrParserWrapper(generated_text);
antlr_parser_wrappers_.push_back(parser);
// Parse the text starting at the opcode_spec_list rule.
auto opcode_spec_vec = parser->parser()->opcode_spec_list()->opcode_spec();
// Process the opcode spec.
for (auto *opcode_spec : opcode_spec_vec) {
ProcessOpcodeSpec(opcode_spec, slot, instruction_vec, deleted_ops_set,
overridden_ops_set);
}
// Clean up.
for (auto *info : range_info_vec) delete info;
return absl::OkStatus();
}
// Helper function to recursively generate the text for the GENERATE opcode
// spec.
std::string InstructionSetVisitor::GenerateOpcodeSpec(
const std::vector<RangeAssignmentInfo *> &range_info_vec, int index,
const std::string &template_str_in) const {
std::string generated;
// Iterate for the number of values.
for (int i = 0; i < range_info_vec[index]->range_values[0].size(); ++i) {
// Copy the template string.
std::string template_str = template_str_in;
// For each ident, perform substitutions in the template copy with the
// current set of values.
int var_index = 0;
int replace_count = 0;
for (auto &re : range_info_vec[index]->range_regexes) {
replace_count += RE2::GlobalReplace(
&template_str, re,
range_info_vec[index]->range_values[var_index++][i]);
}
// If there are multiple range specifications, then recursively call to
// generate the cartesian product with the values of the next value range
// substitutions.
if (range_info_vec.size() > index + 1) {
absl::StrAppend(&generated, GenerateOpcodeSpec(range_info_vec, index + 1,
template_str));
} else {
absl::StrAppend(&generated, template_str);
}
// If there were no replacements, then the range variables weren't used,
// and the template string won't change for any other values in the range.
// This can happen if the range variables aren't referenced in the string.
// Thus, break out of the loop.
if (replace_count == 0) break;
}
return generated;
}
static absl::StatusOr<std::pair<std::string, std::string::size_type>> get_ident(
std::string str, std::string::size_type pos) {
// If the next character is not an alpha or '_' it's an error.
if (!std::isalpha(str[pos]) && (str[pos] != '_')) {
return absl::InternalError(
absl::StrCat("Invalid character in operand name at position ", pos,
" in '", str, "'"));
}
std::string op_name;
while (std::isalnum(str[pos]) || (str[pos] == '_')) {
op_name.push_back(str[pos]);
pos++;
if (pos >= str.size()) {
pos = std::string::npos;
break;
}
}
return std::make_pair(op_name, pos);
}
// This method parses the disasm format string.
absl::Status InstructionSetVisitor::ParseDisasmFormat(std::string format,
Instruction *inst) {
std::string::size_type pos = 0;
std::string::size_type prev = 0;
std::string::size_type length = format.size();
FormatInfo *format_info = nullptr;
// Extract raw text without (between) the '%' specifiers.
DisasmFormat *disasm_fmt = new DisasmFormat();
while ((pos != std::string::npos) &&
((pos = format.find_first_of('%', pos)) != std::string::npos)) {
std::string text = format.substr(prev, pos - prev);
std::string new_text;
for (auto &c : text) {
if (c == '\\') continue;
new_text.push_back(c);
}
disasm_fmt->format_fragment_vec.push_back(new_text);
pos++;
if (pos >= length) break;
// See if it is a simple %opname specifier or an expression.
if (format[pos] == '(') { // This is an expression.
pos++;
if (pos >= length) break;
// Find end of the expression.
std::string::size_type end_pos = pos;
int paren_count = 0;
while (true) {
if (end_pos >= length) break;
if (format[end_pos] == ':') break;
if (format[end_pos] == '(') paren_count++;
if (format[end_pos] == ')') {
if (paren_count == 0) break;
paren_count--;
}
end_pos++;
}
if (end_pos >= length) break;
auto res = ParseFormatExpression(format.substr(pos, end_pos - pos),
inst->opcode());
if (!res.ok()) {
delete disasm_fmt;
return res.status();
}
format_info = res.value();
pos = end_pos;
format_info->number_format = "%d"; // Default number format.
if (format[pos] == ':') {
pos++;
if (pos >= length) break;
end_pos = format.find_first_of(')', pos);
if (end_pos == std::string::npos) break;
auto res = ParseNumberFormat(format.substr(pos, end_pos - pos));
if (!res.ok()) {
delete format_info;
delete disasm_fmt;
return res.status();
}
format_info->number_format = res.value();
pos = end_pos;
}
pos++;
if (pos >= format.size()) {
pos = std::string::npos;
}
format_info->is_formatted = true;
disasm_fmt->format_info_vec.push_back(format_info);
} else { // Simple %opname specifier.
auto res = get_ident(format, pos);
if (!res.ok()) {
delete disasm_fmt;
return res.status();
}
auto [op_name, end_pos] = res.value();
pos = end_pos;
if (!inst->opcode()->op_locator_map().contains(op_name)) {
delete disasm_fmt;
return absl::InternalError(absl::StrCat(
"Invalid operand '", op_name, "' used in format '", format, "'"));
}
auto *format_info = new FormatInfo();
format_info->op_name = op_name;
format_info->is_formatted = false;
disasm_fmt->format_info_vec.push_back(format_info);
}
prev = pos;
}
if (pos != std::string::npos) {
delete format_info;
delete disasm_fmt;
return absl::InternalError(
absl::StrCat("Unexpected end of format string in '", format, "'"));
}
if (prev != std::string::npos) {
std::string text = format.substr(prev);
std::string new_text;
for (auto &c : text) {
if (c == '\\') continue;
new_text.push_back(c);
}
disasm_fmt->format_fragment_vec.push_back(new_text);
}
std::string str;
for (auto &s : disasm_fmt->format_fragment_vec) {
absl::StrAppend(&str, s, ":");
}
int width = 0;
auto count = inst->disasm_format_vec().size();
if (count < disasm_field_widths_.size()) {
auto result = disasm_field_widths_[count]->GetValue();
if (result.ok()) {
auto *value_ptr = std::get_if<int>(&result.value());
width = *value_ptr;
}
}
disasm_fmt->width = width;
inst->AppendDisasmFormat(disasm_fmt);
return absl::OkStatus();
}
static std::string::size_type skip_space(const std::string &str,
std::string::size_type pos) {
if (pos == std::string::npos) return pos;
if (pos >= str.size()) {
return std::string::npos;
}
while ((str[pos] == ' ') || (str[pos] == '\t')) {
pos++;
if (pos >= str.size()) return std::string::npos;
}
return pos;
}
absl::StatusOr<FormatInfo *> InstructionSetVisitor::ParseFormatExpression(
std::string expr, Opcode *op) {
// The format expression is very simple. It is of the form:
// [@+/-] ident | '(' ident <</>> number ')'
// where @ signifies the current instruction address.
// In short, the value of the field can be shifted left or right, then added
// to, or subtracted from, the instruction address.
FormatInfo *format_info = new FormatInfo();
std::string::size_type pos = 0;
pos = skip_space(expr, pos);
if (pos == std::string::npos) {
delete format_info;
return absl::InternalError("Empty format expression");
}
if (expr[pos] == '@') {
pos++;
format_info->use_address = true;
pos = skip_space(expr, pos);
if (pos == std::string::npos) {
return format_info;
}
if (expr[pos] == '-') {
format_info->operation = "-";
} else if (expr[pos] == '+') {
format_info->operation = "+";
} else {
delete format_info;
return absl::InternalError(
absl::StrCat("@ must be followed by a '+' or a '-' in '", expr, "'"));
}
pos++;
}
pos = skip_space(expr, pos);
if (pos == std::string::npos) {
delete format_info;
return absl::InternalError(
absl::StrCat("Malformed expression '", expr, "'"));
}
if (expr[pos] != '(') { // No shift expression.
// Get the field identifier.
auto res = get_ident(expr, pos);
if (!res.ok()) {
delete format_info;
return res.status();
}
auto [ident, new_pos] = res.value();
if (!op->op_locator_map().contains(ident)) {
delete format_info;
return absl::InternalError(absl::StrCat("Invalid operand '", ident,
"' used in format for opcode'",
op->name(), "'"));
}
format_info->op_name = ident;
// Verify that there are no more characters in the expression.
pos = skip_space(expr, new_pos);
if (pos != std::string::npos) {
delete format_info;
return absl::InternalError(
absl::StrCat("Malformed expression '", expr, "'"));
}
} else { // expr[pos] == '('
pos++;
pos = skip_space(expr, pos);
// The input expression has balanced parens, so we don't have to check for
// end of string.
// Get the field identifier.
auto res = get_ident(expr, pos);
if (!res.ok()) {
delete format_info;
return res.status();
}
auto [ident, new_pos] = res.value();
format_info->op_name = ident;
pos = skip_space(expr, new_pos);
// Get the shift direction.
if (expr.substr(pos, 2) == "<<") {
format_info->do_left_shift = true;
} else if (expr.substr(pos, 2) != ">>") {
delete format_info;
return absl::InternalError(
absl::StrCat("Missing shift in expression '", expr, "'"));
}
pos += 2;
// Get the shift amount.
pos = skip_space(expr, pos);
std::string num;
while (std::isdigit(expr[pos])) {
num.push_back(expr[pos]);
pos++;
}
if (num.empty()) {
delete format_info;
return absl::InternalError(
absl::StrCat("Malformed expression - no shift amount '", expr, "'"));
}
format_info->shift_amount = std::stoi(num);
// Verify close paren, and that there aren't any other characters after
// that.
pos = skip_space(expr, pos);
if (expr[pos] != ')') {
delete format_info;
return absl::InternalError(
absl::StrCat("Malformed expression - expected ')' '", expr, "'"));
}
pos++;
pos = skip_space(expr, pos);
if (pos != std::string::npos) {
delete format_info;
return absl::InternalError(absl::StrCat(
"Malformed expression - extra characters after ')' '", expr, "'"));
}
}
return format_info;
}
absl::StatusOr<std::string> InstructionSetVisitor::ParseNumberFormat(
std::string format) {
std::string::size_type pos = 0;
std::string format_string = "%";
bool leading_zero = false;
if (format[pos] == '0') {
leading_zero = true;
format_string.push_back('0');
pos++;
}
// If there's a leading zero, there has to be a width. Signal error
// otherwise.
if (leading_zero && !std::isdigit(format[pos])) {
return absl::InternalError(
absl::StrCat("Format width required when a leading 0 is specified - '",
format.substr(0, pos + 1), "'"));
}
// Read the format width. It's an error if it's three digits, otherwise,
// just roll with it.
if (std::isdigit(format[pos])) {
std::string number = format.substr(pos++, 1);
if (std::isdigit(format[pos])) {
number.push_back(format[pos++]);
if (std::isdigit(format[pos])) {
return absl::InternalError(
absl::StrCat("Format width > than 3 digits not allowed '",
format.substr(0, pos + 1), "'"));
}
}
format_string.append(number);
}
// Read the number base.
if ((format[pos] != 'o') && (format[pos] != 'd') && (format[pos] != 'x') &&
(format[pos] != 'X')) {
return absl::InternalError(
absl::StrCat("Illegal format specifier '", std::string(1, format[pos]),
"' in '", format.substr(0, pos + 1), "'"));
}
format_string.push_back(format[pos++]);
if (pos < format.size()) {
return absl::InternalError(
absl::StrCat("Too many characters in format specifier '", format, "'"));
}
return format_string;
}
// The following methods are used to generate the prologs and epilogs in the
// emitted files.
std::string InstructionSetVisitor::GenerateHdrFileProlog(
absl::string_view file_name, absl::string_view opcode_file_name,
absl::string_view guard_name, absl::string_view encoding_base_name,
const std::vector<std::string> &namespaces) {
std::string output;
absl::StrAppend(&output, "#ifndef ", guard_name,
"\n"
"#define ",
guard_name,
"\n"
"\n"
"#include <functional>\n"
"#include <map>\n"
"#include <vector>\n"
"\n"
"#include \"mpact/sim/generic/arch_state.h\"\n"
"#include "
"\"mpact/sim/generic/instruction.h\"\n"
"#include \"",
opcode_file_name,
"\"\n"
"\n");
for (const auto &namespace_name : namespaces) {
absl::StrAppend(&output, "namespace ", namespace_name, " {\n");
}
absl::StrAppend(
&output,
"\n"
"using ::mpact::sim::generic::Instruction;\n"
"using SemFunc = "
"::mpact::sim::generic::Instruction::SemanticFunction;\n"
"using ::mpact::sim::generic::ArchState;\n"
"using ::mpact::sim::generic::PredicateOperandInterface;\n"
"using ::mpact::sim::generic::SourceOperandInterface;\n"
"using ::mpact::sim::generic::DestinationOperandInterface;\n"
"using ::mpact::sim::generic::ResourceOperandInterface;\n"
"using SimpleResourceVector = std::vector<SimpleResourceEnum>;\n"
"\n");
// Emit encoding base class.
absl::StrAppend(&output, "class ", encoding_base_name, " {\n public:\n");
absl::StrAppend(&output, " virtual ~", encoding_base_name,
"() = default;\n\n");
// Get opcode method.
absl::StrAppend(
&output,
" virtual OpcodeEnum GetOpcode(SlotEnum slot, int entry) = 0;\n");
// Get resource methods.
absl::StrAppend(
&output, " virtual ResourceOperandInterface *GetSimpleResourceOperand",
"(SlotEnum slot, int entry, OpcodeEnum opcode, SimpleResourceVector "
"&resource_vec, int end) { return nullptr;}\n");
absl::StrAppend(
&output,
" virtual ResourceOperandInterface * "
"GetComplexResourceOperand",
"(SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum "
"resource_op, int begin, int end) { return {}; }\n");
absl::StrAppend(
&output,
" virtual std::vector<ResourceOperandInterface *> "
"GetComplexResourceOperands",
"(SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum "
"resource_op, int begin, int end) { return {}; }\n");
// For each operand type, declare the pure virtual method that returns the
// given operand.
absl::StrAppend(&output,
" virtual PredicateOperandInterface *GetPredicate"
"(SlotEnum slot, int entry, OpcodeEnum opcode, PredOpEnum "
"pred_op) { return nullptr; }\n");
absl::StrAppend(&output,
" virtual SourceOperandInterface *GetSource"
"(SlotEnum slot, int entry, OpcodeEnum opcode, SourceOpEnum "
"source_op, int source_no) { return nullptr;}\n");
absl::StrAppend(
&output,
" virtual std::vector<SourceOperandInterface *> GetSources"
"(SlotEnum slot, int entry, OpcodeEnum opcode, ListSourceOpEnum "
"list_source_op, int source_no) { return {};}\n");
absl::StrAppend(&output,
" virtual DestinationOperandInterface *GetDestination"
"(SlotEnum slot, int entry, OpcodeEnum opcode, "
"DestOpEnum list_dest_op, int dest_no, int latency)"
" { return nullptr; }\n");
absl::StrAppend(
&output,
" virtual std::vector<DestinationOperandInterface *> GetDestinations"
"(SlotEnum slot, int entry, OpcodeEnum opcode, "
"ListDestOpEnum dest_op, int dest_no, const std::vector<int> &latency)"
" { return {}; };\n");
// Destination operand latency getter for destination operands with '*'
// as latency.
absl::StrAppend(
&output,
" virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum "
"opcode, DestOpEnum dest_op, int dest_no) { return 0; };\n",
" virtual std::vector<int> GetLatency(SlotEnum slot, int entry, "
"OpcodeEnum "
"opcode, ListDestOpEnum dest_op, int dest_no) { return {0}; }\n");
absl::StrAppend(&output, "};\n\n");
absl::StrAppend(&output,
"using OperandSetter = "
"std::vector<void (*)(Instruction *, ",
encoding_base_name,
"*, OpcodeEnum, SlotEnum, int)>;\n"
"using DisassemblySetter = void(*)(Instruction *);\n"
"using ResourceSetter = void(*)(Instruction *, ",
encoding_base_name,
"*, SlotEnum, int);\n"
"using SemFuncSetter = std::vector<SemFunc>;\n"
"using AttributeSetter = void(*)(Instruction *);\n"
"struct InstructionInfo {\n"
" OperandSetter operand_setter;\n"
" DisassemblySetter disassembly_setter;\n"
" ResourceSetter resource_setter;\n"
" AttributeSetter attribute_setter;\n"
" SemFuncSetter semfunc;\n"
" int instruction_size;\n"
"};\n"
"\n");
return output;
}
std::tuple<std::string, std::string>
InstructionSetVisitor::GenerateEncFilePrologs(
absl::string_view file_name, absl::string_view guard_name,
absl::string_view opcode_file_name, absl::string_view encoding_type_name,
const std::vector<std::string> &namespaces) {
std::string h_output;
std::string cc_output;
absl::StrAppend(&h_output, "#ifndef ", guard_name,
"\n"
"#define ",
guard_name,
"\n"
"\n"
"#include <array>\n"
"#include <string>\n"
"#include <vector>\n"
"\n"
"#include \"absl/status/status.h\"\n"
"#include \"absl/status/statusor.h\"\n"
"#include \"absl/strings/string_view.h\"\n"
"#include \"mpact/sim/util/asm/resolver_interface.h\"\n"
"#include \"re2/re2.h\"\n"
"#include \"re2/set.h\"\n"
"#include \"",
opcode_file_name,
"\"\n"
"\n");
absl::StrAppend(&cc_output, "#include \"", file_name,
"\"\n"
"\n"
"#include <array>\n"
"#include <string>\n"
"#include <vector>\n"
"\n"
"#include \"absl/status/status.h\"\n"
"#include \"absl/status/statusor.h\"\n"
"#include \"absl/strings/string_view.h\"\n"
"#include \"mpact/sim/util/asm/resolver_interface.h\"\n"
"#include \"re2/re2.h\"\n"
"#include \"re2/set.h\"\n"
"#include \"",
opcode_file_name,
"\"\n"
"\n");
for (const auto &namespace_name : namespaces) {
absl::StrAppend(&h_output, "namespace ", namespace_name, " {\n");
absl::StrAppend(&cc_output, "namespace ", namespace_name, " {\n");
}
absl::StrAppend(&h_output, "\n");
absl::StrAppend(&cc_output, "\n");
return {h_output, cc_output};
}
std::string InstructionSetVisitor::GenerateHdrFileEpilog(
absl::string_view guard_name, const std::vector<std::string> &namespaces) {
std::string output;
absl::StrAppend(&output, GenerateNamespaceEpilog(namespaces));
absl::StrAppend(&output, "\n#endif // ", guard_name, "\n");
return output;
}
std::string InstructionSetVisitor::GenerateCcFileProlog(
absl::string_view hdr_file_name,
const std::vector<std::string> &namespaces) {
std::string output;
// Include files.
absl::StrAppend(&output, "#include \"", hdr_file_name, "\"\n");
absl::StrAppend(&output,
"\n#include <array>\n\n"
"#include \"absl/strings/str_format.h\"\n\n");
for (auto &include_file : include_files_) {
absl::StrAppend(&output, "#include ", include_file, "\n");
}
absl::StrAppend(&output, "\n");
// Namespaces.
for (const auto &namespace_name : namespaces) {
absl::StrAppend(&output, "namespace ", namespace_name, " {\n");
}
absl::StrAppend(&output, "\n");
return output;
}
std::string InstructionSetVisitor::GenerateSimpleHdrProlog(
absl::string_view guard_name, const std::vector<std::string> &namespaces) {
std::string output;
absl::StrAppend(&output, "#ifndef ", guard_name, "\n#define ", guard_name,
"\n\n");
for (const auto &namespace_name : namespaces) {
absl::StrAppend(&output, "namespace ", namespace_name, " {\n");
}
absl::StrAppend(&output, "\n");
return output;
}
std::string InstructionSetVisitor::GenerateNamespaceEpilog(
const std::vector<std::string> &namespaces) {
std::string output;
// Close up namespaces.
absl::StrAppend(&output, "\n");
for (auto namespace_name = namespaces.crbegin();
namespace_name != namespaces.crend(); ++namespace_name) {
absl::StrAppend(&output, "} // namespace ", *namespace_name, "\n");
}
return output;
}
absl::Status InstructionSetVisitor::AddConstant(absl::string_view name,
absl::string_view,
TemplateExpression *expr) {
if (constant_map_.contains(name)) {
return absl::AlreadyExistsError(
absl::StrCat("Constant redefinition of '", name, "'"));
}
constant_map_.emplace(name, expr);
return absl::OkStatus();
}
TemplateExpression *InstructionSetVisitor::GetConstExpression(
absl::string_view name) const {
auto iter = constant_map_.find(name);
if (iter == constant_map_.end()) return nullptr;
return iter->second;
}
} // namespace instruction_set
} // namespace machine_description
} // namespace sim
} // namespace mpact