blob: a4468de6d09682f6d342069842c79066ff6c0fa6 [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/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/TableGenBackend.h"
#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));
}
/// Derive the Triple::SubArchType for a canonical GPU record.
static void emitSubArch(raw_ostream &OS, const Record *Rec) {
if (Rec->getValueAsBit("IsPseudoTarget")) {
OS << "Triple::NoSubArch";
return;
}
StringRef Suffix = Rec->getValueAsString("Name");
Suffix.consume_front("gfx");
Suffix.consume_back("-generic");
OS << "Triple::AMDGPUSubArch";
for (char C : Suffix)
OS << ((C == '-') ? '_' : toUpper(C));
}
/// 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 (Name.ends_with("-generic"))
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;
}
// 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();
}
// 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");
}
}
}
}
static void emitR600(raw_ostream &OS, const RecordKeeper &RK) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/true);
validate(Entries);
if (Entries.empty())
return;
OS << "#ifndef R600_GPU\n"
"#define R600_GPU(NAME, ENUM, FEATURES)\n"
"#endif\n\n"
"#ifndef R600_GPU_ALIAS\n"
"#define R600_GPU_ALIAS(NAME, ENUM)\n"
"#endif\n\n";
for (const GPUEntry &E : Entries) {
StringRef Name = E.Rec->getValueAsString("Name");
if (E.IsAlias) {
OS << "R600_GPU_ALIAS(\"" << Name << "\", ";
emitGPUKindEnum(OS, E.Rec->getValueAsString("Alias"));
OS << ")\n";
continue;
}
OS << "R600_GPU(\"" << Name << "\", ";
emitGPUKindEnum(OS, Name);
OS << ", ";
emitFeatureExpr(OS, E.Rec, "R600_FEATURE_NONE");
OS << ")\n";
}
OS << "\n#undef R600_GPU\n"
"#undef R600_GPU_ALIAS\n";
}
// Return \p Entries with the generic-family entries moved after the non-generic
// ones, each group keeping definition order. The GPUKind enum and GPUInfo table
// are positional and rely on the generics forming a contiguous block at the
// end, so both are emitted in this order.
static std::vector<GPUEntry>
orderGenericsLast(ArrayRef<GPUEntry> Entries,
const StringMap<const Record *> &Canonicals) {
std::vector<GPUEntry> Ordered;
Ordered.reserve(Entries.size());
for (const GPUEntry &E : Entries) {
if (!E.isGeneric(Canonicals))
Ordered.push_back(E);
}
for (const GPUEntry &E : Entries) {
if (E.isGeneric(Canonicals))
Ordered.push_back(E);
}
return Ordered;
}
static void emitAMDGPUEntry(raw_ostream &OS, const GPUEntry &E) {
StringRef Name = E.Rec->getValueAsString("Name");
if (E.IsAlias) {
OS << "AMDGPU_GPU_ALIAS(\"" << Name << "\", ";
emitGPUKindEnum(OS, E.Rec->getValueAsString("Alias"));
OS << ")\n";
} else {
OS << "AMDGPU_GPU(\"" << Name << "\", ";
emitGPUKindEnum(OS, Name);
OS << ")\n";
}
}
static void emitAMDGPU(raw_ostream &OS, const RecordKeeper &RK) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/false);
validate(Entries);
if (Entries.empty())
return;
StringMap<const Record *> Canonicals;
for (const GPUEntry &E : Entries) {
if (!E.IsAlias)
Canonicals[E.Rec->getValueAsString("Name")] = E.Rec;
}
OS << "#ifndef AMDGPU_GPU\n"
"#define AMDGPU_GPU(NAME, ENUM)\n"
"#endif\n\n"
"#ifndef AMDGPU_GPU_ALIAS\n"
"#define AMDGPU_GPU_ALIAS(NAME, ENUM)\n"
"#endif\n\n";
for (const GPUEntry &E : orderGenericsLast(Entries, Canonicals))
emitAMDGPUEntry(OS, E);
OS << "\n#undef AMDGPU_GPU\n"
"#undef AMDGPU_GPU_ALIAS\n";
}
/// Emit a GPUInfo table indexed by (GPUKind - AMDGPUFirstGPUKind).
static void emitAMDGPUTable(raw_ostream &OS, const RecordKeeper &RK) {
std::vector<GPUEntry> Entries = collectGPUs(RK, /*WantR600=*/false);
if (Entries.empty())
return;
StringMap<const Record *> Canonicals;
for (const GPUEntry &E : Entries) {
if (!E.IsAlias)
Canonicals[E.Rec->getValueAsString("Name")] = E.Rec;
}
// Canonicals only; aliases share a canonical's GPUKind row.
std::vector<const Record *> Canon;
for (const GPUEntry &E : orderGenericsLast(Entries, Canonicals)) {
if (!E.IsAlias)
Canon.push_back(E.Rec);
}
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 << " {\"" << Name << "\", ";
emitSubArch(OS, R);
OS << ", ";
emitFeatureExpr(OS, R, "FEATURE_NONE");
OS << ", ";
emitIsaVersion(OS, R, '{', '}');
OS << ", \"" << getArchFamily(R) << "\"},\n";
}
OS << "};\n"
"#endif // GET_AMDGPU_GPU_TABLE\n\n";
}
static void emitAMDGPUTargetDef(const RecordKeeper &RK, raw_ostream &OS) {
OS << "// Autogenerated by AMDGPUTargetDefEmitter.cpp\n\n";
// R600 processors are Processor records; AMDGPU processors are
// ProcessorModel records. R600.td and AMDGPU.td are separate top-level files
// (neither includes the other), so exactly one family is present in a given
// run; the other section emits nothing.
emitR600(OS, RK);
emitAMDGPU(OS, RK);
emitAMDGPUTable(OS, RK);
}
static TableGen::Emitter::Opt X("gen-amdgpu-target-def", emitAMDGPUTargetDef,
"Generate the list of AMDGPU GPUs");