| //===- 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"); |