//===- ARMTargetDefEmitter.cpp - Generate data about ARM Architectures ----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend exports information about CPUs, FPUs, architectures,
// and features into a common format that can be used by both TargetParser and
// the ARM and AArch64 backends.
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <cstdint>
#include <set>
#include <string>

using namespace llvm;

/// Collect the full set of implied features for a SubtargetFeature.
static void CollectImpliedFeatures(std::set<Record *> &SeenFeats, Record *Rec) {
  assert(Rec->isSubClassOf("SubtargetFeature") &&
         "Rec is not a SubtargetFeature");

  SeenFeats.insert(Rec);
  for (Record *Implied : Rec->getValueAsListOfDefs("Implies"))
    CollectImpliedFeatures(SeenFeats, Implied);
}

static void CheckFeatureTree(Record *Root) {
  std::set<Record *> SeenFeats;
  CollectImpliedFeatures(SeenFeats, Root);

  // Check that each of the mandatory (implied) features which is an
  // ExtensionWithMArch is also enabled by default.
  auto DefaultExtsVec = Root->getValueAsListOfDefs("DefaultExts");
  std::set<Record *> DefaultExts{DefaultExtsVec.begin(), DefaultExtsVec.end()};
  for (auto *Feat : SeenFeats) {
    if (Feat->isSubClassOf("ExtensionWithMArch") && !DefaultExts.count(Feat))
      PrintFatalError(Root->getLoc(),
                      "ExtensionWithMArch " + Feat->getName() +
                          " is implied (mandatory) as a SubtargetFeature, but "
                          "is not present in DefaultExts");
  }
}

static void EmitARMTargetDef(RecordKeeper &RK, raw_ostream &OS) {
  OS << "// Autogenerated by ARMTargetDefEmitter.cpp\n\n";

  // Look through all SubtargetFeature defs with the given FieldName, and
  // collect the set of all Values that that FieldName is set to.
  auto gatherSubtargetFeatureFieldValues = [&RK](StringRef FieldName) {
    llvm::StringSet<> Set;
    for (const Record *Rec : RK.getAllDerivedDefinitions("SubtargetFeature")) {
      if (Rec->getValueAsString("FieldName") == FieldName) {
        Set.insert(Rec->getValueAsString("Value"));
      }
    }
    return Set;
  };

  // Sort the extensions alphabetically, so they don't appear in tablegen order.
  std::vector<Record *> SortedExtensions =
      RK.getAllDerivedDefinitions("Extension");
  auto Alphabetical = [](Record *A, Record *B) -> bool {
    const auto NameA = A->getValueAsString("Name");
    const auto NameB = B->getValueAsString("Name");
    return NameA.compare(NameB) < 0; // A lexographically less than B
  };
  std::sort(SortedExtensions.begin(), SortedExtensions.end(), Alphabetical);

  // The ARMProcFamilyEnum values are initialised by SubtargetFeature defs
  // which set the ARMProcFamily field. We can generate the enum from these defs
  // which look like this:
  //
  // def ProcA5      : SubtargetFeature<"a5", "ARMProcFamily", "CortexA5",
  //                                    "Cortex-A5 ARM processors", []>;
  OS << "#ifndef ARM_PROCESSOR_FAMILY\n"
     << "#define ARM_PROCESSOR_FAMILY(ENUM)\n"
     << "#endif\n\n";
  const StringSet<> ARMProcFamilyVals =
      gatherSubtargetFeatureFieldValues("ARMProcFamily");
  for (const StringRef &Family : ARMProcFamilyVals.keys())
    OS << "ARM_PROCESSOR_FAMILY(" << Family << ")\n";
  OS << "\n#undef ARM_PROCESSOR_FAMILY\n\n";

  OS << "#ifndef ARM_ARCHITECTURE\n"
     << "#define ARM_ARCHITECTURE(ENUM)\n"
     << "#endif\n\n";
  // This should correspond to instances of the Architecture tablegen class.
  const StringSet<> ARMArchVals = gatherSubtargetFeatureFieldValues("ARMArch");
  for (const StringRef &Arch : ARMArchVals.keys())
    OS << "ARM_ARCHITECTURE(" << Arch << ")\n";
  OS << "\n#undef ARM_ARCHITECTURE\n\n";

  // Currently only AArch64 (not ARM) is handled beyond this point.
  if (!RK.getClass("Architecture64"))
    return;

  // Emit the ArchExtKind enum
  OS << "#ifdef EMIT_ARCHEXTKIND_ENUM\n"
     << "enum ArchExtKind : unsigned {\n";
  for (const Record *Rec : SortedExtensions) {
    auto AEK = Rec->getValueAsString("ArchExtKindSpelling").upper();
    OS << "  " << AEK << ",\n";
  }
  OS << "  AEK_NUM_EXTENSIONS\n"
     << "};\n"
     << "#undef EMIT_ARCHEXTKIND_ENUM\n"
     << "#endif // EMIT_ARCHEXTKIND_ENUM\n";

  // Emit information for each defined Extension; used to build ArmExtKind.
  OS << "#ifdef EMIT_EXTENSIONS\n"
     << "inline constexpr ExtensionInfo Extensions[] = {\n";
  for (const Record *Rec : SortedExtensions) {
    auto AEK = Rec->getValueAsString("ArchExtKindSpelling").upper();
    OS << "  ";
    OS << "{\"" << Rec->getValueAsString("UserVisibleName") << "\"";
    if (auto Alias = Rec->getValueAsString("UserVisibleAlias"); Alias.empty())
      OS << ", {}";
    else
      OS << ", \"" << Alias << "\"";
    OS << ", AArch64::" << AEK;
    OS << ", \"" << Rec->getValueAsString("ArchFeatureName") << "\"";
    OS << ", \"" << Rec->getValueAsString("Desc") << "\"";
    OS << ", \"+" << Rec->getValueAsString("Name") << "\""; // posfeature
    OS << ", \"-" << Rec->getValueAsString("Name") << "\""; // negfeature
    OS << "},\n";
  };
  OS << "};\n"
     << "#undef EMIT_EXTENSIONS\n"
     << "#endif // EMIT_EXTENSIONS\n"
     << "\n";

  // Emit FMV information
  auto FMVExts = RK.getAllDerivedDefinitionsIfDefined("FMVExtension");
  OS << "#ifdef EMIT_FMV_INFO\n"
     << "const std::vector<llvm::AArch64::FMVInfo>& "
        "llvm::AArch64::getFMVInfo() {\n"
     << "  static std::vector<FMVInfo> I;\n"
     << "  if(I.size()) return I;\n"
     << "  I.reserve(" << FMVExts.size() << ");\n";
  for (const Record *Rec : FMVExts) {
    OS << "  I.emplace_back(";
    OS << "\"" << Rec->getValueAsString("Name") << "\"";
    OS << ", " << Rec->getValueAsString("Bit");
    OS << ", \"" << Rec->getValueAsString("BackendFeatures") << "\"";
    OS << ", " << (uint64_t)Rec->getValueAsInt("Priority");
    OS << ");\n";
  };
  OS << "  return I;\n"
     << "}\n"
     << "#undef EMIT_FMV_INFO\n"
     << "#endif // EMIT_FMV_INFO\n"
     << "\n";

  // Emit extension dependencies
  OS << "#ifdef EMIT_EXTENSION_DEPENDENCIES\n"
     << "inline constexpr ExtensionDependency ExtensionDependencies[] = {\n";
  for (const Record *Rec : SortedExtensions) {
    auto LaterAEK = Rec->getValueAsString("ArchExtKindSpelling").upper();
    for (const Record *I : Rec->getValueAsListOfDefs("Implies"))
      if (auto EarlierAEK = I->getValueAsOptionalString("ArchExtKindSpelling"))
        OS << "  {" << EarlierAEK->upper() << ", " << LaterAEK << "},\n";
  }
  // FIXME: Tablegen has the Subtarget Feature FeatureRCPC_IMMO which is implied
  // by FeatureRCPC3 and in turn implies FeatureRCPC. The proper fix is to make
  // FeatureRCPC_IMMO an Extension but that will expose it to the command line.
  OS << "  {AEK_RCPC, AEK_RCPC3},\n";
  OS << "};\n"
     << "#undef EMIT_EXTENSION_DEPENDENCIES\n"
     << "#endif // EMIT_EXTENSION_DEPENDENCIES\n"
     << "\n";

  // Emit architecture information
  OS << "#ifdef EMIT_ARCHITECTURES\n";

  // Return the C++ name of the of an ArchInfo object
  auto ArchInfoName = [](int Major, int Minor,
                         StringRef Profile) -> std::string {
    return Minor == 0 ? "ARMV" + std::to_string(Major) + Profile.upper()
                      : "ARMV" + std::to_string(Major) + "_" +
                            std::to_string(Minor) + Profile.upper();
  };

  auto Architectures = RK.getAllDerivedDefinitionsIfDefined("Architecture64");
  std::vector<std::string> CppSpellings;
  for (const Record *Rec : Architectures) {
    const int Major = Rec->getValueAsInt("Major");
    const int Minor = Rec->getValueAsInt("Minor");
    const std::string ProfileLower = Rec->getValueAsString("Profile").str();
    const std::string ProfileUpper = Rec->getValueAsString("Profile").upper();

    if (ProfileLower != "a" && ProfileLower != "r")
      PrintFatalError(Rec->getLoc(),
                      "error: Profile must be one of 'a' or 'r', got '" +
                          ProfileLower + "'");

    // Name of the object in C++
    const std::string CppSpelling = ArchInfoName(Major, Minor, ProfileUpper);
    OS << "inline constexpr ArchInfo " << CppSpelling << " = {\n";
    CppSpellings.push_back(CppSpelling);

    OS << llvm::format("  VersionTuple{%d, %d},\n", Major, Minor);
    OS << llvm::format("  %sProfile,\n", ProfileUpper.c_str());

    // Name as spelled for -march.
    if (Minor == 0)
      OS << llvm::format("  \"armv%d-%s\",\n", Major, ProfileLower.c_str());
    else
      OS << llvm::format("  \"armv%d.%d-%s\",\n", Major, Minor,
                         ProfileLower.c_str());

    // SubtargetFeature::Name, used for -target-feature. Here the "+" is added.
    const auto TargetFeatureName = Rec->getValueAsString("Name");
    OS << "  \"+" << TargetFeatureName << "\",\n";

    // Construct the list of default extensions
    OS << "  (AArch64::ExtensionBitset({";
    for (auto *E : Rec->getValueAsListOfDefs("DefaultExts")) {
      OS << "AArch64::" << E->getValueAsString("ArchExtKindSpelling").upper()
         << ", ";
    }
    OS << "}))\n";

    OS << "};\n";
  }

  OS << "\n"
     << "/// The set of all architectures\n"
     << "static constexpr std::array<const ArchInfo *, " << CppSpellings.size()
     << "> ArchInfos = {\n";
  for (StringRef CppSpelling : CppSpellings)
    OS << "  &" << CppSpelling << ",\n";
  OS << "};\n";

  OS << "#undef EMIT_ARCHITECTURES\n"
     << "#endif // EMIT_ARCHITECTURES\n"
     << "\n";

  // Emit CPU Aliases
  OS << "#ifdef EMIT_CPU_ALIAS\n"
     << "inline constexpr Alias CpuAliases[] = {\n";

  llvm::StringSet<> Processors;
  for (const Record *Rec : RK.getAllDerivedDefinitions("ProcessorModel"))
    Processors.insert(Rec->getValueAsString("Name"));

  llvm::StringSet<> Aliases;
  for (const Record *Rec : RK.getAllDerivedDefinitions("ProcessorAlias")) {
    auto Name = Rec->getValueAsString("Name");
    auto Alias = Rec->getValueAsString("Alias");
    if (!Processors.contains(Alias))
      PrintFatalError(
          Rec, "Alias '" + Name + "' references a non-existent ProcessorModel '" + Alias + "'");
    if (Processors.contains(Name))
      PrintFatalError(
          Rec, "Alias '" + Name + "' duplicates an existing ProcessorModel");
    if (!Aliases.insert(Name).second)
      PrintFatalError(
          Rec, "Alias '" + Name + "' duplicates an existing ProcessorAlias");

    OS << llvm::formatv(R"(  { "{0}", "{1}" },)", Name, Alias) << '\n';
  }

  OS << "};\n"
     << "#undef EMIT_CPU_ALIAS\n"
     << "#endif // EMIT_CPU_ALIAS\n"
     << "\n";

  // Emit CPU information
  OS << "#ifdef EMIT_CPU_INFO\n"
     << "inline constexpr CpuInfo CpuInfos[] = {\n";

  for (const Record *Rec : RK.getAllDerivedDefinitions("ProcessorModel")) {
    auto Name = Rec->getValueAsString("Name");
    auto Features = Rec->getValueAsListOfDefs("Features");

    // "apple-latest" is backend-only, should not be accepted by TargetParser.
    if (Name == "apple-latest")
      continue;

    Record *Arch;
    if (Name == "generic") {
      // "generic" is an exception. It does not have an architecture, and there
      // are tests that depend on e.g. -mattr=-v8.4a meaning HasV8_0aOps==false.
      // However, in TargetParser CPUInfo, it is written as 8.0-A.
      Arch = RK.getDef("HasV8_0aOps");
    } else {
      // Search for an Architecture64 in the list of features.
      auto IsArch = [](Record *F) { return F->isSubClassOf("Architecture64"); };
      auto ArchIter = llvm::find_if(Features, IsArch);
      if (ArchIter == Features.end())
        PrintFatalError(Rec, "Features must include an Architecture64.");
      Arch = *ArchIter;

      // Check there is only one Architecture in the list.
      if (llvm::count_if(Features, IsArch) > 1)
        PrintFatalError(Rec, "Features has multiple Architecture64 entries");
    }

    auto Major = Arch->getValueAsInt("Major");
    auto Minor = Arch->getValueAsInt("Minor");
    auto Profile = Arch->getValueAsString("Profile");
    auto ArchInfo = ArchInfoName(Major, Minor, Profile);

    CheckFeatureTree(Arch);

    OS << "  {\n"
       << "    \"" << Name << "\",\n"
       << "    " << ArchInfo << ",\n"
       << "    AArch64::ExtensionBitset({\n";

    // Keep track of extensions we have seen
    StringSet<> SeenExts;
    for (auto *E : Rec->getValueAsListOfDefs("Features"))
      // Only process subclasses of Extension
      if (E->isSubClassOf("Extension")) {
        const auto AEK = E->getValueAsString("ArchExtKindSpelling").upper();
        if (!SeenExts.insert(AEK).second)
          PrintFatalError(Rec, "feature already added: " + E->getName());
        OS << "      AArch64::" << AEK << ",\n";
      }
    OS << "    })\n"
       << "  },\n";
  }
  OS << "};\n";

  OS << "#undef EMIT_CPU_INFO\n"
     << "#endif // EMIT_CPU_INFO\n"
     << "\n";
}

static TableGen::Emitter::Opt
    X("gen-arm-target-def", EmitARMTargetDef,
      "Generate the ARM or AArch64 Architecture information header.");
