blob: b303a415521cbf07e7338b9cb7768eeca9306644 [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/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.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));
}
// 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);
}
// Derive the Triple::SubArchType for a canonical GPU record. A pseudo target
// represents no hardware and maps to Triple::NoSubArch; otherwise the subarch
// is derived from the name.
static void emitSubArch(raw_ostream &OS, const Record *Rec) {
if (Rec->getValueAsBit("IsPseudoTarget")) {
OS << "Triple::NoSubArch";
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();
}
// The gfx family for a canonical GPU record: the "-generic" family prefix (e.g.
// "gfx9-4-generic" -> "gfx9"), or the name with its last two chars dropped for
// a concrete GPU (e.g. "gfx90a" -> "gfx9", "gfx1030" -> "gfx10"). Empty for a
// pseudo target.
static StringRef getArchFamily(const Record *Rec) {
if (Rec->getValueAsBit("IsPseudoTarget"))
return "";
StringRef Name = Rec->getValueAsString("Name");
if (isGenericTarget(Rec))
return Name.take_front(Name.find('-'));
return Name.drop_back(2);
}
// 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");
}
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" (stepping is a single lowercase hex digit).
static void emitConcreteSubArchTripleName(raw_ostream &OS, const Record *Rec) {
std::vector<int64_t> V = Rec->getValueAsListOfInts("IsaVersion");
// Assuming emitIsaVersion validated the number of elements.
if (V[2] < 0 || V[2] > 15) {
PrintFatalError(Rec->getLoc(), "GPU '" + Rec->getValueAsString("Name") +
"' stepping must be a single hex digit");
}
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
// Emit the ArchFeature spellings joined with '|', or \p NoneSpelling when
// empty.
static void emitFeatureExpr(raw_ostream &OS, const Record *Rec,
StringRef NoneSpelling) {
ListSeparator LS("|");
bool Any = false;
for (const Record *F : Rec->getValueAsListOfDefs("ArchFeatures")) {
OS << LS << F->getValueAsString("Spelling");
Any = true;
}
if (!Any)
OS << NoneSpelling;
}
// 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) {
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"))
<< ", ";
emitFeatureExpr(OS, R, "R600_FEATURE_NONE");
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";
}
/// 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) {
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 << ", ";
emitFeatureExpr(OS, R, "FEATURE_NONE");
OS << ", ";
emitIsaVersion(OS, R, '{', '}');
OS << ", " << Names.GetOrAddStringOffset(getArchFamily(R)) << "},\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");
{
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.
SmallString<16> TripleName;
raw_svector_ostream TripleOS(TripleName);
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) {
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);
emitR600Table(TablesOS, RK, Names);
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);
emitAMDGPUTable(TablesOS, RK, Names);
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");