blob: 376222cf754fb21653244aa3c7467460ce282b65 [file] [edit]
//===- AMDGPUTargetDefEmitter.cpp - Generate lists of AMDGPU GPUs ---------===//
//
// 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 emits the AMDGPU GPU tables used by
// AMDGPUTargetParser.cpp.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/StringToOffsetTable.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <string>
#include <utility>
#include <vector>
using namespace llvm;
// Derive the GPUKind enum from a processor name, e.g. "gfx90a" -> "GK_GFX90A".
static void emitGPUKindEnum(raw_ostream &OS, StringRef Name) {
OS << "GK_";
for (char C : Name)
OS << ((C == '-') ? '_' : toUpper(C));
}
// Feature string to enumerator, e.g. "16-bit-insts" -> "FEAT_16_BIT_INSTS".
// AMDGCN uses the "FEAT_" prefix, R600 the "R600_FEAT_" prefix.
static void emitFeatureEnum(raw_ostream &OS, StringRef Prefix, StringRef Name) {
OS << Prefix;
for (char C : Name)
OS << ((C == '-') ? '_' : toUpper(C));
}
// Emit the Triple::AMDGPUSubArch enumerator suffix for a "gfx..." GPU name,
// e.g. "gfx90a" -> "90A", "gfx9-generic" -> "9" (the family major).
static void emitSubArchSuffix(raw_ostream &OS, StringRef Name) {
StringRef Suffix = Name;
Suffix.consume_front("gfx");
Suffix.consume_back("-generic");
for (char C : Suffix)
OS << static_cast<char>((C == '-') ? '_' : toUpper(C));
}
/// Derive the Triple::SubArchType from a "gfx..." GPU name, e.g. "gfx90a" ->
/// Triple::AMDGPUSubArch90A
static void emitSubArchForName(raw_ostream &OS, StringRef Name) {
OS << "Triple::AMDGPUSubArch";
emitSubArchSuffix(OS, Name);
}
// The explicit subarch spelling for a GPU whose subarch is not derivable from
// its name, or empty. Optional so test stubs may omit it.
static std::optional<StringRef> getSubArchSpelling(const Record *Rec) {
return Rec->getValueAsOptionalString("SubArchSpelling");
}
// Emit a subarch enumerator suffix for a spelling, dropping '.' and upcasing,
// e.g. "12.50s" -> "1250S", matching the sibling name-derived enumerators.
static void emitSpellingSuffix(raw_ostream &OS, StringRef Spelling) {
for (char C : Spelling)
if (C != '.')
OS << static_cast<char>(toUpper(C));
}
// Derive the Triple::SubArchType for a canonical GPU record. A pseudo target
// maps to Triple::NoSubArch; an explicit SubArchSpelling maps to that (e.g.
// "4.67q" -> AMDGPUSubArch4_67Q); otherwise it is derived from the name.
static void emitSubArch(raw_ostream &OS, const Record *Rec) {
if (Rec->getValueAsBit("IsPseudoTarget")) {
OS << "Triple::NoSubArch";
return;
}
if (std::optional<StringRef> Spelling = getSubArchSpelling(Rec)) {
OS << "Triple::AMDGPUSubArch";
emitSpellingSuffix(OS, *Spelling);
return;
}
emitSubArchForName(OS, Rec->getValueAsString("Name"));
}
// A canonical GPU record is a "gfxN-generic" family target if it covers a set
// of concrete GPUs (via CoveredGPUs) rather than being a single piece of
// hardware.
static bool isGenericTarget(const Record *Rec) {
return !Rec->getValueAsListOfDefs("CoveredGPUs").empty();
}
// Emit the gfx family for a canonical GPU record: "gfx" + the ISA major version
// (e.g. "gfx90a"/[9,0,10] -> "gfx9", "gfx1250"/[12,5,0] -> "gfx12").
// Nothing for a pseudo target.
static void emitArchFamily(raw_ostream &OS, const Record *Rec) {
if (Rec->getValueAsBit("IsPseudoTarget"))
return;
OS << "gfx" << Rec->getValueAsListOfInts("IsaVersion")[0];
}
// Emit the ISA version tuple as "major, minor, stepping" wrapped in \p Open and
// \p Close (parens for the AMDGPU_GPU macro's ISAVERSION argument, braces for a
// struct initializer).
static void emitIsaVersion(raw_ostream &OS, const Record *Rec, char Open,
char Close) {
std::vector<int64_t> V = Rec->getValueAsListOfInts("IsaVersion");
if (V.size() != 3) {
PrintFatalError(Rec->getLoc(),
"GPU '" + Rec->getValueAsString("Name") +
"' must have a 3-element [major, minor, stepping] "
"IsaVersion");
}
// Each component is stored in a uint8_t field, and the stepping is
// additionally spelled as a single lowercase hex digit in the device and
// subarch names. Reject out-of-range values.
for (int64_t Component : V) {
if (!isUInt<8>(Component)) {
PrintFatalError(Rec->getLoc(),
"GPU '" + Rec->getValueAsString("Name") +
"' IsaVersion components must each fit in a byte");
}
}
if (!isUInt<4>(V[2])) {
PrintFatalError(Rec->getLoc(), "GPU '" + Rec->getValueAsString("Name") +
"' stepping must be a single hex digit");
}
OS << Open << V[0] << ", " << V[1] << ", " << V[2] << Close;
}
// Emit the triple subarch name for a concrete GPU, e.g. gfx90c / [9, 0, 12] ->
// "amdgpu9.0c". The stepping is spelled as a single lowercase hex digit
// (validated by emitIsaVersion).
static void emitConcreteSubArchTripleName(raw_ostream &OS, const Record *Rec) {
std::vector<int64_t> V = Rec->getValueAsListOfInts("IsaVersion");
OS << "amdgpu" << V[0] << '.' << V[1] << hexdigit(V[2], /*LowerCase=*/true);
}
// Emit the triple subarch name for a major-family subarch, e.g. "9" ->
// "amdgpu9", "9_4" -> "amdgpu9.4" (the enumerator suffix uses '_', the triple
// name '.').
static void emitFamilySubArchTripleName(raw_ostream &OS, StringRef Suffix) {
OS << "amdgpu";
for (char C : Suffix)
OS << static_cast<char>((C == '_') ? '.' : C);
}
// A canonical GPU or a ProcessorAlias.
namespace {
struct GPUEntry {
const Record *Rec;
bool IsAlias;
// Whether this entry is (or aliases) a generic family target. \p Canonicals
// maps canonical GPU names to their records.
bool isGeneric(const StringMap<const Record *> &Canonicals) const {
const Record *Canon =
IsAlias ? Canonicals.lookup(Rec->getValueAsString("Alias")) : Rec;
return Canon && isGenericTarget(Canon);
}
};
} // namespace
// The frontend-visible features from def \p ListName, in bit order. Empty if
// the def is absent.
static std::vector<const Record *>
collectFrontendFeatures(const RecordKeeper &RK, StringRef ListName) {
const Record *List = RK.getDef(ListName);
if (!List)
return {};
return List->getValueAsListOfDefs("Features");
}
static void
emitFeatureBitset(raw_ostream &OS, StringRef BitsetType, StringRef EnumPrefix,
const Record *GPU,
const DenseMap<const Record *, unsigned> &FeatureIdx);
// The transitive closure of a GPU's SubtargetFeatures, following the Implies
// edges (a feature enables everything it implies).
static void collectFeatureClosure(const Record *GPU,
SetVector<const Record *> &Closure) {
std::vector<const Record *> Worklist = GPU->getValueAsListOfDefs("Features");
while (!Worklist.empty()) {
const Record *F = Worklist.back();
Worklist.pop_back();
if (Closure.insert(F))
append_range(Worklist, F->getValueAsListOfDefs("Implies"));
}
}
// Collect canonical GPUs and their aliases, in TableGen definition order. R600
// GPUs are plain Processor records; AMDGPU GPUs are ProcessorModel records (a
// Processor subclass), so \p WantR600 selects the family to emit.
static std::vector<GPUEntry> collectGPUs(const RecordKeeper &RK,
bool WantR600) {
ArrayRef<const Record *> GPUs = RK.getAllDerivedDefinitions("AMDGPUGPUInfo");
std::vector<GPUEntry> Entries;
Entries.reserve(GPUs.size());
for (const Record *Rec : GPUs) {
if (Rec->isSubClassOf("ProcessorModel") == WantR600)
continue;
Entries.push_back({Rec, /*IsAlias=*/false});
}
// Aliases only make sense when their canonical is present, so only gather
// them for the family being emitted.
if (!Entries.empty()) {
for (const Record *Rec :
RK.getAllDerivedDefinitionsIfDefined("ProcessorAlias"))
Entries.push_back({Rec, /*IsAlias=*/true});
}
// Sort to preserve declaration order instead of name order.
sort(Entries, [](const GPUEntry &A, const GPUEntry &B) {
return A.Rec->getID() < B.Rec->getID();
});
return Entries;
}
// Check that every alias resolves to a canonical GPU and no name repeats.
static void validate(ArrayRef<GPUEntry> Entries) {
StringMap<const Record *> Canonicals;
for (const GPUEntry &E : Entries)
if (!E.IsAlias)
Canonicals[E.Rec->getValueAsString("Name")] = E.Rec;
StringMap<const Record *> Seen;
for (const GPUEntry &E : Entries) {
StringRef Name = E.Rec->getValueAsString("Name");
if (!Seen.insert({Name, E.Rec}).second) {
PrintFatalError(E.Rec->getLoc(),
"duplicate AMDGPU processor name '" + Name + "'");
}
if (E.IsAlias) {
StringRef Alias = E.Rec->getValueAsString("Alias");
if (!Canonicals.count(Alias)) {
PrintFatalError(E.Rec->getLoc(),
"ProcessorAlias '" + Name + "' aliases '" + Alias +
"' which is not a canonical AMDGPU GPU");
}
}
}
}
// The canonical R600 GPU records, in GPUKind-enum / TableGen definition order.
static std::vector<const Record *>
collectR600Canonicals(const RecordKeeper &RK) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/true);
std::vector<const Record *> Canon;
Canon.reserve(Entries.size());
for (const GPUEntry &E : Entries) {
if (!E.IsAlias)
Canon.push_back(E.Rec);
}
return Canon;
}
// Emit the R600 GPUKind enumerators (canonical GPUs only; aliases share a
// canonical's kind). Guarded by GET_R600_GPU_ENUM.
static void emitR600Enum(raw_ostream &OS, const RecordKeeper &RK) {
std::vector<const Record *> Canon = collectR600Canonicals(RK);
if (Canon.empty())
return;
OS << "#ifdef GET_R600_GPU_ENUM\n"
"#undef GET_R600_GPU_ENUM\n";
for (const Record *R : Canon) {
OS << " ";
emitGPUKindEnum(OS, R->getValueAsString("Name"));
OS << ",\n";
}
OS << "#endif // GET_R600_GPU_ENUM\n\n";
}
// Emit the R600Info table indexed by (GPUKind - R600FirstGPUKind). Names are
// offsets into the shared \p Names table. Guarded by GET_R600_GPU_TABLE.
static void
emitR600Table(raw_ostream &OS, const RecordKeeper &RK,
StringToOffsetTable &Names,
const DenseMap<const Record *, unsigned> &FeatureIdx) {
std::vector<const Record *> Canon = collectR600Canonicals(RK);
if (Canon.empty())
return;
OS << "#ifdef GET_R600_GPU_TABLE\n"
"#undef GET_R600_GPU_TABLE\n";
OS << "static constexpr GPUKind R600FirstGPUKind = ";
emitGPUKindEnum(OS, Canon.front()->getValueAsString("Name"));
OS << ";\n"
"static constexpr R600Info R600GPUTable[] = {\n";
for (const Record *R : Canon) {
OS << " {" << Names.GetOrAddStringOffset(R->getValueAsString("Name"))
<< ", ";
emitFeatureBitset(OS, "R600FeatureBitset", "R600_FEAT_", R, FeatureIdx);
OS << "},\n";
}
OS << "};\n"
"#endif // GET_R600_GPU_TABLE\n\n";
}
// Emit the R600 name -> GPUKind alias table. Guarded by
// GET_R600_GPU_ALIAS_TABLE; names are offsets into \p Names.
static void emitR600Aliases(raw_ostream &OS, const RecordKeeper &RK,
StringToOffsetTable &Names) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/true);
validate(Entries);
if (Entries.empty())
return;
OS << "#ifdef GET_R600_GPU_ALIAS_TABLE\n"
"#undef GET_R600_GPU_ALIAS_TABLE\n"
"static constexpr GPUNameAlias R600GPUAliases[] = {\n";
for (const GPUEntry &E : Entries) {
if (!E.IsAlias)
continue;
OS << " {" << Names.GetOrAddStringOffset(E.Rec->getValueAsString("Name"))
<< ", ";
emitGPUKindEnum(OS, E.Rec->getValueAsString("Alias"));
OS << "},\n";
}
OS << "};\n"
"#endif // GET_R600_GPU_ALIAS_TABLE\n\n";
}
// Canonical AMDGPU GPUs in GPUKind-enum order: non-generic targets first, then
// the "gfxN-generic" targets. The enum and the GPUInfo table share this order.
static std::vector<const Record *>
collectAMDGPUCanonicals(const RecordKeeper &RK) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/false);
std::vector<const Record *> Canon;
Canon.reserve(Entries.size());
for (const GPUEntry &E : Entries) {
if (!E.IsAlias && !isGenericTarget(E.Rec))
Canon.push_back(E.Rec);
}
for (const GPUEntry &E : Entries) {
if (!E.IsAlias && isGenericTarget(E.Rec))
Canon.push_back(E.Rec);
}
return Canon;
}
// Emit the AMDGPU GPUKind enumerators (canonical GPUs only; aliases share a
// canonical's kind). Guarded by GET_AMDGPU_GPU_ENUM.
static void emitAMDGPUEnum(raw_ostream &OS, const RecordKeeper &RK) {
std::vector<const Record *> Canon = collectAMDGPUCanonicals(RK);
if (Canon.empty())
return;
OS << "#ifdef GET_AMDGPU_GPU_ENUM\n"
"#undef GET_AMDGPU_GPU_ENUM\n";
for (const Record *R : Canon) {
OS << " ";
emitGPUKindEnum(OS, R->getValueAsString("Name"));
OS << ",\n";
}
OS << "#endif // GET_AMDGPU_GPU_ENUM\n\n";
}
// Emit the name -> GPUKind alias table (legacy names such as "tahiti" ->
// gfx600). Guarded by GET_AMDGPU_GPU_ALIAS_TABLE; names are offsets into \p
// Names.
static void emitAMDGPUAliases(raw_ostream &OS, const RecordKeeper &RK,
StringToOffsetTable &Names) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/false);
validate(Entries);
if (Entries.empty())
return;
OS << "#ifdef GET_AMDGPU_GPU_ALIAS_TABLE\n"
"#undef GET_AMDGPU_GPU_ALIAS_TABLE\n"
"static constexpr GPUNameAlias AMDGPUGPUAliases[] = {\n";
for (const GPUEntry &E : Entries) {
if (!E.IsAlias)
continue;
OS << " {" << Names.GetOrAddStringOffset(E.Rec->getValueAsString("Name"))
<< ", ";
emitGPUKindEnum(OS, E.Rec->getValueAsString("Alias"));
OS << "},\n";
}
OS << "};\n"
"#endif // GET_AMDGPU_GPU_ALIAS_TABLE\n\n";
}
// Per-family spellings for the generated feature enum and name table. R600 and
// AMDGCN each get their own so the two headers coexist.
struct FeatureNaming {
StringRef EnumGuard;
StringRef EnumPrefix;
StringRef CountEnumerator;
StringRef NameTableGuard;
StringRef NameTableSymbol;
};
static constexpr FeatureNaming AMDGPUFeatureNaming = {
"GET_AMDGPU_FEATURE_ENUM", "FEAT_", "NUM_FEATURES",
"GET_AMDGPU_FEATURE_NAME_TABLE", "AMDGPUFeatureNames"};
static constexpr FeatureNaming R600FeatureNaming = {
"GET_R600_FEATURE_ENUM", "R600_FEAT_", "R600_NUM_FEATURES",
"GET_R600_FEATURE_NAME_TABLE", "R600FeatureNames"};
// Emit the frontend feature enum for a family, interning each feature name into
// \p Names. Returns the name offsets indexed by feature bit.
static std::vector<unsigned> emitFeatureEnum(raw_ostream &OS,
const FeatureNaming &Naming,
ArrayRef<const Record *> Features,
StringToOffsetTable &Names) {
std::vector<unsigned> Offsets;
if (Features.empty())
return Offsets;
Offsets.reserve(Features.size());
OS << "#ifdef " << Naming.EnumGuard << "\n"
<< "#undef " << Naming.EnumGuard << "\n";
for (const Record *F : Features) {
StringRef Name = F->getValueAsString("Name");
OS << " ";
emitFeatureEnum(OS, Naming.EnumPrefix, Name);
OS << ",\n";
Offsets.push_back(Names.GetOrAddStringOffset(Name));
}
OS << " " << Naming.CountEnumerator << "\n"
<< "#endif // " << Naming.EnumGuard << "\n\n";
return Offsets;
}
// Emit a family's feature-name table (bit -> name offset).
static void emitFeatureNames(raw_ostream &OS, const FeatureNaming &Naming,
ArrayRef<unsigned> Offsets) {
if (Offsets.empty())
return;
OS << "#ifdef " << Naming.NameTableGuard << "\n"
<< "#undef " << Naming.NameTableGuard << "\n"
<< "static constexpr StringTable::Offset " << Naming.NameTableSymbol
<< "[] = {\n";
for (unsigned O : Offsets)
OS << " " << O << ",\n";
OS << "};\n"
<< "#endif // " << Naming.NameTableGuard << "\n\n";
}
// Features checked for generic-target compatibility: frontend-visible features
// and features explicitly opting into the any-covered-GPU rule.
static SetVector<const Record *>
collectGenericFeatures(const Record *GPU,
const DenseMap<const Record *, unsigned> &FeatureIdx) {
SetVector<const Record *> Closure;
collectFeatureClosure(GPU, Closure);
SetVector<const Record *> Features;
for (const Record *F : Closure) {
if (FeatureIdx.contains(F) || F->isSubClassOf("AMDGPUGenericAnyFeature"))
Features.insert(F);
}
return Features;
}
// Ordinary frontend-visible features must be present on every covered GPU.
// AMDGPUGenericAnyFeature features need only be present on one covered GPU.
static void
validateGenericFeatures(const Record *GPU,
const DenseMap<const Record *, unsigned> &FeatureIdx) {
StringRef Name = GPU->getValueAsString("Name");
std::vector<const Record *> Covered =
GPU->getValueAsListOfDefs("CoveredGPUs");
if (Covered.empty()) {
if (Name.starts_with("gfx") && Name.ends_with("-generic")) {
PrintFatalError(GPU->getLoc(), "generic target '" + Name +
"' must cover at least one GPU");
}
return;
}
SetVector<const Record *> GenericFeatures =
collectGenericFeatures(GPU, FeatureIdx);
SetVector<const Record *> CoveredFeatures;
for (const Record *Member : Covered) {
if (!Member->isSubClassOf("AMDGPUGPUInfo") ||
!Member->isSubClassOf("ProcessorModel") ||
Member->getValueAsBit("IsPseudoTarget") || isGenericTarget(Member)) {
PrintFatalError(GPU->getLoc(),
"generic target '" + Name + "' covers '" +
Member->getValueAsString("Name") +
"', which is not a concrete AMDGPU GPU");
}
SetVector<const Record *> MemberFeatures =
collectGenericFeatures(Member, FeatureIdx);
CoveredFeatures.insert_range(MemberFeatures);
for (const Record *F : GenericFeatures) {
if (!F->isSubClassOf("AMDGPUGenericAnyFeature") &&
!MemberFeatures.contains(F)) {
PrintFatalError(GPU->getLoc(),
"generic target '" + GPU->getValueAsString("Name") +
"' exposes feature '" +
F->getValueAsString("Name") +
"' not supported by covered GPU '" +
Member->getValueAsString("Name") + "'");
}
}
}
for (const Record *F : GenericFeatures) {
if (!CoveredFeatures.contains(F)) {
PrintFatalError(GPU->getLoc(),
"generic target '" + GPU->getValueAsString("Name") +
"' exposes feature '" + F->getValueAsString("Name") +
"' not supported by any covered GPU");
}
}
}
static void validateAMDGPU(const RecordKeeper &RK) {
DenseMap<const Record *, unsigned> FeatureIdx;
for (const auto &[Idx, F] :
enumerate(collectFrontendFeatures(RK, "AMDGPUFrontendVisibleFeatures")))
FeatureIdx[F] = Idx;
for (const Record *GPU : RK.getAllDerivedDefinitions("AMDGPUGPUInfo"))
validateGenericFeatures(GPU, FeatureIdx);
}
// Emit a GPU's feature bitset initializer: its feature closure intersected with
// the frontend-visible set \p FeatureIdx, e.g.
// "AMDGPUFeatureBitset({FEAT_DPP, FEAT_CI_INSTS})".
static void
emitFeatureBitset(raw_ostream &OS, StringRef BitsetType, StringRef EnumPrefix,
const Record *GPU,
const DenseMap<const Record *, unsigned> &FeatureIdx) {
SetVector<const Record *> Closure;
collectFeatureClosure(GPU, Closure);
// Sort by bit index for stable output.
SmallVector<std::pair<unsigned, StringRef>> Bits;
for (const Record *F : Closure) {
auto It = FeatureIdx.find(F);
if (It != FeatureIdx.end())
Bits.emplace_back(It->second, F->getValueAsString("Name"));
}
sort(Bits);
OS << BitsetType << "({";
ListSeparator LS(", ");
for (const auto &[Idx, Name] : Bits) {
OS << LS;
emitFeatureEnum(OS, EnumPrefix, Name);
}
OS << "})";
}
// The value of the SubtargetFeature in \p GPU's closure that sets \p FieldName,
// or \p Default if it has none. Two features setting the same field to
// different values is an error: SubtargetFeature silently takes the larger.
static int64_t getFeatureValue(const Record *GPU, StringRef FieldName,
int64_t Default) {
SetVector<const Record *> Closure;
collectFeatureClosure(GPU, Closure);
const Record *Found = nullptr;
int64_t Value = Default;
for (const Record *F : Closure) {
if (F->getValueAsString("FieldName") != FieldName)
continue;
int64_t V;
if (!to_integer(F->getValueAsString("Value"), V)) {
PrintFatalError(F->getLoc(), "feature '" + F->getValueAsString("Name") +
"' must have an integer value");
}
if (Found && V != Value) {
PrintFatalError(GPU->getLoc(),
"GPU '" + GPU->getValueAsString("Name") +
"' gets conflicting '" + FieldName +
"' values from '" + Found->getValueAsString("Name") +
"' and '" + F->getValueAsString("Name") + "'");
}
Found = F;
Value = V;
}
return Value;
}
/// Emit a GPUInfo table indexed by (GPUKind - AMDGPUFirstGPUKind). Name and
/// family strings are stored as offsets into the shared \p Names table.
static void
emitAMDGPUTable(raw_ostream &OS, const RecordKeeper &RK,
StringToOffsetTable &Names,
const DenseMap<const Record *, unsigned> &FeatureIdx) {
std::vector<const Record *> Canon = collectAMDGPUCanonicals(RK);
if (Canon.empty())
return;
OS << "#ifdef GET_AMDGPU_GPU_TABLE\n"
"#undef GET_AMDGPU_GPU_TABLE\n";
OS << "static constexpr GPUKind AMDGPUFirstGPUKind = ";
emitGPUKindEnum(OS, Canon.front()->getValueAsString("Name"));
OS << ";\n"
"static constexpr GPUInfo AMDGPUGPUTable[] = {\n";
for (const Record *R : Canon) {
StringRef Name = R->getValueAsString("Name");
OS << " {" << Names.GetOrAddStringOffset(Name) << ", ";
emitSubArch(OS, R);
OS << ", ";
emitFeatureBitset(OS, "AMDGPUFeatureBitset", "FEAT_", R, FeatureIdx);
OS << ", ";
emitIsaVersion(OS, R, '{', '}');
SmallString<16> Family;
raw_svector_ostream FamilyOS(Family);
emitArchFamily(FamilyOS, R);
OS << ", " << Names.GetOrAddStringOffset(Family) << ", "
<< getFeatureValue(R, "MaxWavesPerEU", 10) << ", "
<< getFeatureValue(R, "AddressableLocalMemorySize", 32768) << ", "
<< getFeatureValue(R, "LDSBankCount", 32) << ", "
<< getFeatureValue(R, "BufferResourceNumRecordsWidth", 0) << "},\n";
}
OS << "};\n"
"#endif // GET_AMDGPU_GPU_TABLE\n\n";
}
// Emit the subarch -> major-family-subarch overrides for getMajorSubArch (a
// subarch not listed here is its own major). Each member GPU maps to its
// family's major, sourced from a "gfxN-generic" target's CoveredGPUs, or from
// an AMDGPUFamily's MajorSubArch for the gfx6/gfx7/gfx8 families that have no
// generic target.
static void emitAMDGPUMajorSubArch(raw_ostream &OS, const RecordKeeper &RK) {
ArrayRef<const Record *> GPUs =
RK.getAllDerivedDefinitionsIfDefined("AMDGPUGPUInfo");
ArrayRef<const Record *> Families =
RK.getAllDerivedDefinitionsIfDefined("AMDGPUFamily");
// The overrides come from generic targets' CoveredGPUs and AMDGPUFamily
// members. std::array makes the R600 case (zero entries) well-formed.
size_t NumEntries = 0;
for (const Record *G : GPUs)
NumEntries += G->getValueAsListOfDefs("CoveredGPUs").size();
for (const Record *F : Families)
NumEntries += F->getValueAsListOfDefs("Members").size();
OS << "#ifdef GET_AMDGPU_MAJOR_SUBARCH\n"
"#undef GET_AMDGPU_MAJOR_SUBARCH\n"
"struct AMDGPUMajorSubArchEntry {\n"
" Triple::SubArchType SubArch;\n"
" Triple::SubArchType Major;\n"
"};\n"
"static constexpr std::array<AMDGPUMajorSubArchEntry, "
<< NumEntries << "> AMDGPUMajorSubArch = {{\n";
// A "gfxN-generic" target's subarch is the major for every GPU it covers.
for (const Record *G : GPUs) {
for (const Record *Member : G->getValueAsListOfDefs("CoveredGPUs")) {
OS << " {";
emitSubArchForName(OS, Member->getValueAsString("Name"));
OS << ", ";
emitSubArch(OS, G);
OS << "},\n";
}
}
// The gfx6/gfx7/gfx8 families have no generic target, so their major comes
// from AMDGPUFamily::MajorSubArch.
for (const Record *F : Families) {
StringRef Major = F->getValueAsString("MajorSubArch");
for (const Record *Member : F->getValueAsListOfDefs("Members")) {
OS << " {";
emitSubArchForName(OS, Member->getValueAsString("Name"));
OS << ", Triple::AMDGPUSubArch" << Major << "},\n";
}
}
OS << "}};\n"
"#endif // GET_AMDGPU_MAJOR_SUBARCH\n\n";
}
/// Emit the canonical GPU name for each AMDGPU subarch ("gfx900"), and it's
/// corresponding subarch ("amdgpu9.00")
static void emitAMDGPUSubArchNames(raw_ostream &OS, const RecordKeeper &RK,
StringToOffsetTable &Names) {
// A row of the generated table. \p Suffix is emitted verbatim after
// "Triple::AMDGPUSubArch"; the two name offsets index the shared string pool.
struct SubArchEntry {
SmallString<16> Suffix;
StringRef GPUName; // e.g. "gfx900".
unsigned TripleNameOffset;
};
std::vector<SubArchEntry> Entries;
for (const GPUEntry &E : collectGPUs(RK, /*WantR600=*/false)) {
if (E.IsAlias || E.Rec->getValueAsBit("IsPseudoTarget"))
continue;
SubArchEntry Entry;
Entry.GPUName = E.Rec->getValueAsString("Name");
SmallString<16> TripleName;
raw_svector_ostream TripleOS(TripleName);
// An explicit subarch spelling supplies the enumerator suffix and triple
// name, rather than the name/ISA version.
if (std::optional<StringRef> Spelling = getSubArchSpelling(E.Rec)) {
raw_svector_ostream SubArchOS(Entry.Suffix);
emitSpellingSuffix(SubArchOS, *Spelling);
TripleOS << "amdgpu" << *Spelling;
} else {
{
raw_svector_ostream SubArchOS(Entry.Suffix);
emitSubArchSuffix(SubArchOS, Entry.GPUName);
}
// A "gfxN-generic" target maps to the major-family subarch, so it takes
// the family triple name; a concrete GPU derives it from the ISA version.
if (isGenericTarget(E.Rec))
emitFamilySubArchTripleName(TripleOS, Entry.Suffix);
else
emitConcreteSubArchTripleName(TripleOS, E.Rec);
}
Entry.TripleNameOffset = Names.GetOrAddStringOffset(TripleName);
Entries.push_back(std::move(Entry));
}
for (const Record *F : RK.getAllDerivedDefinitionsIfDefined("AMDGPUFamily")) {
std::vector<const Record *> Members = F->getValueAsListOfDefs("Members");
StringRef Major = F->getValueAsString("MajorSubArch");
SubArchEntry Entry;
Entry.Suffix = Major;
Entry.GPUName = Members.front()->getValueAsString("Name");
SmallString<16> TripleName;
raw_svector_ostream TripleOS(TripleName);
emitFamilySubArchTripleName(TripleOS, Major);
Entry.TripleNameOffset = Names.GetOrAddStringOffset(TripleName);
Entries.push_back(std::move(Entry));
}
if (Entries.empty())
return;
unsigned NoSubArchOffset = Names.GetOrAddStringOffset("amdgpu");
OS << "#ifdef GET_AMDGPU_SUBARCH_NAME\n"
"#undef GET_AMDGPU_SUBARCH_NAME\n";
OS << "static constexpr StringTable::Offset AMDGPUNoSubArchNameOffset = "
<< NoSubArchOffset << ";\n";
OS << "struct AMDGPUSubArchNameEntry {\n"
" Triple::SubArchType SubArch;\n"
" StringTable::Offset NameOffset;\n"
" StringTable::Offset TripleNameOffset;\n"
"};\n"
"static constexpr AMDGPUSubArchNameEntry AMDGPUSubArchNames[] = {\n";
for (const SubArchEntry &E : Entries)
OS << " {Triple::AMDGPUSubArch" << E.Suffix << ", "
<< Names.GetOrAddStringOffset(E.GPUName) << ", " << E.TripleNameOffset
<< "},\n";
OS << "};\n"
"#endif // GET_AMDGPU_SUBARCH_NAME\n\n";
}
static void emitAMDGPUTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
validateAMDGPU(RK);
OS << "// Autogenerated by AMDGPUTargetDefEmitter.cpp\n\n";
// R600.td and AMDGPU.td are separate top-level files, so a run sees exactly
// one family; the other family's sections emit nothing.
emitR600Enum(OS, RK);
emitAMDGPUEnum(OS, RK);
emitAMDGPUMajorSubArch(OS, RK);
// Each family gets its own string pool with a distinct guard/symbol so the
// two generated headers stay independent when a consumer includes both.
// Buffer the tables first to intern their strings, then emit the pool ahead.
{
StringToOffsetTable Names;
std::string Tables;
raw_string_ostream TablesOS(Tables);
// The R600 frontend feature enum and per-GPU bitsets share the R600 string
// pool (feature names live alongside GPU names).
std::vector<const Record *> Features =
collectFrontendFeatures(RK, "R600FrontendVisibleFeatures");
DenseMap<const Record *, unsigned> FeatureIdx;
for (const auto &[Idx, F] : enumerate(Features))
FeatureIdx[F] = Idx;
std::vector<unsigned> FeatureOffsets =
emitFeatureEnum(TablesOS, R600FeatureNaming, Features, Names);
emitR600Table(TablesOS, RK, Names, FeatureIdx);
emitFeatureNames(TablesOS, R600FeatureNaming, FeatureOffsets);
emitR600Aliases(TablesOS, RK, Names);
if (!Tables.empty()) {
OS << "#ifdef GET_R600_NAME_TABLE\n"
"#undef GET_R600_NAME_TABLE\n";
Names.EmitStringTableDef(OS, "R600NameTable");
OS << "#endif // GET_R600_NAME_TABLE\n\n";
OS << Tables;
}
}
{
StringToOffsetTable Names;
std::string Tables;
raw_string_ostream TablesOS(Tables);
// The frontend feature enum and per-GPU bitsets share the AMDGPU string
// pool (feature names live alongside GPU names).
std::vector<const Record *> Features =
collectFrontendFeatures(RK, "AMDGPUFrontendVisibleFeatures");
DenseMap<const Record *, unsigned> FeatureIdx;
for (const auto &[Idx, F] : enumerate(Features))
FeatureIdx[F] = Idx;
std::vector<unsigned> FeatureOffsets =
emitFeatureEnum(TablesOS, AMDGPUFeatureNaming, Features, Names);
emitAMDGPUTable(TablesOS, RK, Names, FeatureIdx);
emitFeatureNames(TablesOS, AMDGPUFeatureNaming, FeatureOffsets);
emitAMDGPUAliases(TablesOS, RK, Names);
emitAMDGPUSubArchNames(TablesOS, RK, Names);
if (!Tables.empty()) {
OS << "#ifdef GET_AMDGPU_NAME_TABLE\n"
"#undef GET_AMDGPU_NAME_TABLE\n";
Names.EmitStringTableDef(OS, "AMDGPUNameTable");
OS << "#endif // GET_AMDGPU_NAME_TABLE\n\n";
OS << Tables;
}
}
}
static TableGen::Emitter::Opt X("gen-amdgpu-target-def", emitAMDGPUTargetDef,
"Generate the list of AMDGPU GPUs");