| //===- SearchableTableEmitter.cpp - Generate efficiently searchable tables -==// | |
| // | |
| // 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 a generic array initialized by specified fields, | |
| // together with companion index tables and lookup functions. The lookup | |
| // function generated is either a direct lookup (when a single primary key field | |
| // is integral and densely numbered) or a binary search otherwise. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "Basic/CodeGenIntrinsics.h" | |
| #include "Common/CodeGenTarget.h" | |
| #include "llvm/ADT/ArrayRef.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/MapVector.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/TableGen/Error.h" | |
| #include "llvm/TableGen/Record.h" | |
| #include "llvm/TableGen/StringToOffsetTable.h" | |
| #include "llvm/TableGen/TableGenBackend.h" | |
| #include <set> | |
| #include <string> | |
| #include <vector> | |
| using namespace llvm; | |
| #define DEBUG_TYPE "searchable-table-emitter" | |
| static int64_t getAsInt(const Init *B) { | |
| if (const auto *BI = dyn_cast<BitsInit>(B)) | |
| return *BI->convertInitializerToInt(); | |
| if (const auto *II = dyn_cast<IntInit>(B)) | |
| return II->getValue(); | |
| llvm_unreachable("Unexpected initializer"); | |
| } | |
| static int64_t getInt(const Record *R, StringRef Field) { | |
| return getAsInt(R->getValueInit(Field)); | |
| } | |
| namespace { | |
| struct GenericEnum { | |
| struct Entry { | |
| StringRef Name; | |
| int64_t Value; | |
| Entry(StringRef N, int64_t V) : Name(N), Value(V) {} | |
| }; | |
| std::string Name; | |
| const Record *Class = nullptr; | |
| std::string PreprocessorGuard; | |
| MapVector<const Record *, Entry> Entries; | |
| std::string UnderlyingType; | |
| const Entry *getEntry(const Record *Def) const { | |
| auto II = Entries.find(Def); | |
| if (II == Entries.end()) | |
| return nullptr; | |
| return &II->second; | |
| } | |
| }; | |
| struct GenericField { | |
| std::string Name; | |
| const RecTy *RecType = nullptr; | |
| bool IsCode = false; | |
| bool IsIntrinsic = false; | |
| bool IsInstruction = false; | |
| GenericEnum *Enum = nullptr; | |
| GenericField(StringRef Name) : Name(Name.str()) {} | |
| }; | |
| struct SearchIndex { | |
| std::string Name; | |
| SMLoc Loc; // Source location of PrimaryKey or Key field definition. | |
| SmallVector<GenericField, 1> Fields; | |
| bool EarlyOut = false; | |
| bool ReturnRange = false; | |
| }; | |
| struct GenericTable { | |
| std::string Name; | |
| ArrayRef<SMLoc> Locs; // Source locations from the Record instance. | |
| std::string PreprocessorGuard; | |
| std::string CppTypeName; | |
| SmallVector<GenericField, 2> Fields; | |
| std::vector<const Record *> Entries; | |
| std::unique_ptr<SearchIndex> PrimaryKey; | |
| SmallVector<std::unique_ptr<SearchIndex>, 2> Indices; | |
| bool AllowSparseTable; | |
| const GenericField *getFieldByName(StringRef Name) const { | |
| for (const auto &Field : Fields) { | |
| if (Name == Field.Name) | |
| return &Field; | |
| } | |
| return nullptr; | |
| } | |
| }; | |
| class SearchableTableEmitter { | |
| const RecordKeeper &Records; | |
| std::unique_ptr<CodeGenTarget> Target; | |
| std::vector<std::unique_ptr<GenericEnum>> Enums; | |
| DenseMap<const Record *, GenericEnum *> EnumMap; | |
| std::set<std::string> PreprocessorGuards; | |
| public: | |
| explicit SearchableTableEmitter(const RecordKeeper &R) : Records(R) {} | |
| void run(raw_ostream &OS); | |
| private: | |
| using SearchTableEntry = std::pair<const Init *, int>; | |
| enum TypeContext { | |
| TypeInStaticStruct, | |
| TypeInTempStruct, | |
| TypeInArgument, | |
| }; | |
| std::string primaryRepresentation(SMLoc Loc, const GenericField &Field, | |
| const Init *I) { | |
| if (const auto *SI = dyn_cast<StringInit>(I)) { | |
| if (Field.IsCode || SI->hasCodeFormat()) | |
| return SI->getValue().str(); | |
| else | |
| return SI->getAsString(); | |
| } | |
| if (const auto *BI = dyn_cast<BitsInit>(I)) | |
| return "0x" + utohexstr(getAsInt(BI)); | |
| if (const auto *BI = dyn_cast<BitInit>(I)) | |
| return BI->getValue() ? "true" : "false"; | |
| if (Field.IsIntrinsic) | |
| return "Intrinsic::" + getIntrinsic(I).EnumName.str(); | |
| if (Field.IsInstruction) | |
| return I->getAsString(); | |
| if (Field.Enum) { | |
| const GenericEnum::Entry *Entry = | |
| Field.Enum->getEntry(cast<DefInit>(I)->getDef()); | |
| if (!Entry) | |
| PrintFatalError(Loc, | |
| Twine("Entry for field '") + Field.Name + "' is null"); | |
| return Entry->Name.str(); | |
| } | |
| PrintFatalError(Loc, Twine("invalid field type for field '") + Field.Name + | |
| "'; expected: bit, bits, string, or code"); | |
| } | |
| bool isIntrinsic(const Init *I) { | |
| if (const auto *DI = dyn_cast<DefInit>(I)) | |
| return DI->getDef()->isSubClassOf("Intrinsic"); | |
| return false; | |
| } | |
| const CodeGenIntrinsic &getIntrinsic(const Init *I) { | |
| const Record *Def = cast<DefInit>(I)->getDef(); | |
| return Target->getIntrinsic(Def); | |
| } | |
| bool compareBy(const Record *LHS, const Record *RHS, | |
| const SearchIndex &Index); | |
| std::string searchableFieldType(const GenericTable &Table, | |
| const SearchIndex &Index, | |
| const GenericField &Field, TypeContext Ctx) { | |
| if (isa<StringRecTy>(Field.RecType)) { | |
| if (Ctx == TypeInStaticStruct) | |
| return "unsigned"; | |
| if (Ctx == TypeInTempStruct) | |
| return "std::string"; | |
| return "StringRef"; | |
| } | |
| if (const auto *BI = dyn_cast<BitsRecTy>(Field.RecType)) { | |
| unsigned NumBits = BI->getNumBits(); | |
| if (NumBits <= 8) | |
| return "uint8_t"; | |
| if (NumBits <= 16) | |
| return "uint16_t"; | |
| if (NumBits <= 32) | |
| return "uint32_t"; | |
| if (NumBits <= 64) | |
| return "uint64_t"; | |
| PrintFatalError(Index.Loc, Twine("In table '") + Table.Name + | |
| "' lookup method '" + Index.Name + | |
| "', key field '" + Field.Name + | |
| "' of type bits is too large"); | |
| } | |
| if (isa<BitRecTy>(Field.RecType)) | |
| return "bool"; | |
| if (Field.Enum || Field.IsIntrinsic || Field.IsInstruction) | |
| return "unsigned"; | |
| PrintFatalError(Index.Loc, | |
| Twine("In table '") + Table.Name + "' lookup method '" + | |
| Index.Name + "', key field '" + Field.Name + | |
| "' has invalid type: " + Field.RecType->getAsString()); | |
| } | |
| void emitGenericTable(const GenericTable &Table, raw_ostream &OS); | |
| void emitGenericEnum(const GenericEnum &Enum, raw_ostream &OS); | |
| void emitLookupDeclaration(const GenericTable &Table, | |
| const SearchIndex &Index, raw_ostream &OS); | |
| void emitLookupFunction(const GenericTable &Table, const SearchIndex &Index, | |
| bool IsPrimary, StringToOffsetTable &StrTab, | |
| raw_ostream &OS); | |
| void emitIfdef(StringRef Guard, raw_ostream &OS); | |
| bool parseFieldType(GenericField &Field, const Init *II); | |
| std::unique_ptr<SearchIndex> | |
| parseSearchIndex(GenericTable &Table, const RecordVal *RecVal, StringRef Name, | |
| ArrayRef<StringRef> Key, bool EarlyOut, bool ReturnRange); | |
| void collectEnumEntries(GenericEnum &Enum, StringRef NameField, | |
| StringRef ValueField, ArrayRef<const Record *> Items); | |
| void collectTableEntries(GenericTable &Table, ArrayRef<const Record *> Items); | |
| int64_t getNumericKey(const SearchIndex &Index, const Record *Rec); | |
| }; | |
| } // End anonymous namespace. | |
| // For search indices that consists of a single field whose numeric value is | |
| // known, return that numeric value. | |
| int64_t SearchableTableEmitter::getNumericKey(const SearchIndex &Index, | |
| const Record *Rec) { | |
| assert(Index.Fields.size() == 1); | |
| const GenericField &Field = Index.Fields[0]; | |
| // To be consistent with compareBy and primaryRepresentation elsewhere, | |
| // we check for IsInstruction before Enum-- these fields are not exclusive. | |
| if (Field.IsInstruction) { | |
| const Record *TheDef = Rec->getValueAsDef(Field.Name); | |
| return Target->getInstrIntValue(TheDef); | |
| } | |
| if (Field.Enum) { | |
| const Record *EnumEntry = Rec->getValueAsDef(Field.Name); | |
| return Field.Enum->getEntry(EnumEntry)->Value; | |
| } | |
| assert(isa<BitsRecTy>(Field.RecType) && "unexpected field type"); | |
| return getInt(Rec, Field.Name); | |
| } | |
| /// Less-than style comparison between \p LHS and \p RHS according to the | |
| /// key of \p Index. | |
| bool SearchableTableEmitter::compareBy(const Record *LHS, const Record *RHS, | |
| const SearchIndex &Index) { | |
| // Compare two values and return: | |
| // * -1 if LHS < RHS. | |
| // * 1 if LHS > RHS. | |
| // * 0 if LHS == RHS. | |
| auto CmpLTValue = [](const auto &LHS, const auto &RHS) -> int { | |
| if (LHS < RHS) | |
| return -1; | |
| if (LHS > RHS) | |
| return 1; | |
| return 0; | |
| }; | |
| // Specialized form of `CmpLTValue` for string-like types that uses compare() | |
| // to do the comparison of the 2 strings once (instead if 2 comparisons if we | |
| // use `CmpLTValue`). | |
| auto CmpLTString = [](StringRef LHS, StringRef RHS) -> int { | |
| return LHS.compare(RHS); | |
| }; | |
| // Compare two fields and returns: | |
| // - true if LHS < RHS. | |
| // - false if LHS > RHS. | |
| // - std::nullopt if LHS == RHS. | |
| auto CmpLTField = [this, &Index, &CmpLTValue, | |
| &CmpLTString](const Init *LHSI, const Init *RHSI, | |
| const GenericField &Field) -> int { | |
| if (isa<BitsRecTy>(Field.RecType) || isa<IntRecTy>(Field.RecType)) { | |
| int64_t LHSi = getAsInt(LHSI); | |
| int64_t RHSi = getAsInt(RHSI); | |
| return CmpLTValue(LHSi, RHSi); | |
| } | |
| if (Field.IsIntrinsic) { | |
| const CodeGenIntrinsic &LHSi = getIntrinsic(LHSI); | |
| const CodeGenIntrinsic &RHSi = getIntrinsic(RHSI); | |
| if (int Cmp = CmpLTString(LHSi.TargetPrefix, RHSi.TargetPrefix)) | |
| return Cmp; | |
| return CmpLTString(LHSi.Name, RHSi.Name); | |
| } | |
| if (Field.IsInstruction) { | |
| // This does not correctly compare the predefined instructions! | |
| const Record *LHSr = cast<DefInit>(LHSI)->getDef(); | |
| const Record *RHSr = cast<DefInit>(RHSI)->getDef(); | |
| // Order pseudo instructions before non-pseudo ones. | |
| bool LHSNotPseudo = !LHSr->getValueAsBit("isPseudo"); | |
| bool RHSNotPseudo = !RHSr->getValueAsBit("isPseudo"); | |
| if (int Cmp = CmpLTValue(LHSNotPseudo, RHSNotPseudo)) | |
| return Cmp; | |
| return CmpLTString(LHSr->getName(), RHSr->getName()); | |
| } | |
| if (Field.Enum) { | |
| const Record *LHSr = cast<DefInit>(LHSI)->getDef(); | |
| const Record *RHSr = cast<DefInit>(RHSI)->getDef(); | |
| int64_t LHSv = Field.Enum->getEntry(LHSr)->Value; | |
| int64_t RHSv = Field.Enum->getEntry(RHSr)->Value; | |
| return CmpLTValue(LHSv, RHSv); | |
| } | |
| std::string LHSs = primaryRepresentation(Index.Loc, Field, LHSI); | |
| std::string RHSs = primaryRepresentation(Index.Loc, Field, RHSI); | |
| if (isa<StringRecTy>(Field.RecType)) { | |
| LHSs = StringRef(LHSs).upper(); | |
| RHSs = StringRef(RHSs).upper(); | |
| } | |
| return CmpLTString(LHSs, RHSs); | |
| }; | |
| for (const GenericField &Field : Index.Fields) { | |
| const Init *LHSI = LHS->getValueInit(Field.Name); | |
| const Init *RHSI = RHS->getValueInit(Field.Name); | |
| if (int Cmp = CmpLTField(LHSI, RHSI, Field)) | |
| return Cmp < 0; | |
| } | |
| return false; | |
| } | |
| void SearchableTableEmitter::emitIfdef(StringRef Guard, raw_ostream &OS) { | |
| OS << "#ifdef " << Guard << "\n"; | |
| PreprocessorGuards.insert(Guard.str()); | |
| } | |
| /// Emit a generic enum. | |
| void SearchableTableEmitter::emitGenericEnum(const GenericEnum &Enum, | |
| raw_ostream &OS) { | |
| emitIfdef((Twine("GET_") + Enum.PreprocessorGuard + "_DECL").str(), OS); | |
| OS << "enum " << Enum.Name; | |
| if (!Enum.UnderlyingType.empty()) | |
| OS << " : " << Enum.UnderlyingType; | |
| OS << " {\n"; | |
| for (const auto &[Name, Value] : | |
| make_second_range(Enum.Entries.getArrayRef())) | |
| OS << " " << Name << " = " << Value << ",\n"; | |
| OS << "};\n"; | |
| OS << "#endif\n\n"; | |
| } | |
| void SearchableTableEmitter::emitLookupFunction(const GenericTable &Table, | |
| const SearchIndex &Index, | |
| bool IsPrimary, | |
| StringToOffsetTable &StrTab, | |
| raw_ostream &OS) { | |
| OS << "\n"; | |
| emitLookupDeclaration(Table, Index, OS); | |
| OS << " {\n"; | |
| std::vector<const Record *> IndexRowsStorage; | |
| ArrayRef<const Record *> IndexRows; | |
| StringRef IndexTypeName; | |
| StringRef IndexName; | |
| if (IsPrimary) { | |
| IndexTypeName = Table.CppTypeName; | |
| IndexName = Table.Name; | |
| IndexRows = Table.Entries; | |
| } else { | |
| OS << " struct IndexType {\n"; | |
| for (const auto &Field : Index.Fields) { | |
| OS << " " | |
| << searchableFieldType(Table, Index, Field, TypeInStaticStruct) << " " | |
| << Field.Name << ";\n"; | |
| } | |
| OS << " unsigned _index;\n"; | |
| OS << " };\n"; | |
| OS << " static const struct IndexType Index[] = {\n"; | |
| std::vector<std::pair<const Record *, unsigned>> Entries; | |
| Entries.reserve(Table.Entries.size()); | |
| for (auto [Idx, TblEntry] : enumerate(Table.Entries)) | |
| Entries.emplace_back(TblEntry, Idx); | |
| llvm::stable_sort(Entries, | |
| [&](const std::pair<const Record *, unsigned> &LHS, | |
| const std::pair<const Record *, unsigned> &RHS) { | |
| return compareBy(LHS.first, RHS.first, Index); | |
| }); | |
| IndexRowsStorage.reserve(Entries.size()); | |
| for (const auto &[EntryRec, EntryIndex] : Entries) { | |
| IndexRowsStorage.push_back(EntryRec); | |
| OS << " { "; | |
| ListSeparator LS; | |
| for (const auto &Field : Index.Fields) { | |
| const Init *Value = EntryRec->getValueInit(Field.Name); | |
| std::string Repr = primaryRepresentation(Index.Loc, Field, Value); | |
| if (isa<StringRecTy>(Field.RecType)) { | |
| // TODO: if most strings are lower-case already, we can save space by | |
| // converting all strings to lower case instead. If strings are not | |
| // already all-uppercase, we currently store them twice -- but if most | |
| // strings are all-lowercase, we can use the lowercase variant for | |
| // case-insenstive comparison. | |
| OS << LS | |
| << StrTab.GetOrAddStringOffset( | |
| StringRef(Value->getAsUnquotedString()).upper()) | |
| << " /* " << StringRef(Repr).upper() << " */"; | |
| } else { | |
| OS << LS << Repr; | |
| } | |
| } | |
| OS << ", " << EntryIndex << " },\n"; | |
| } | |
| OS << " };\n\n"; | |
| IndexTypeName = "IndexType"; | |
| IndexName = "Index"; | |
| IndexRows = IndexRowsStorage; | |
| } | |
| bool IsContiguous = false; | |
| if (Index.Fields.size() == 1 && | |
| (Index.Fields[0].Enum || isa<BitsRecTy>(Index.Fields[0].RecType) || | |
| Index.Fields[0].IsInstruction)) { | |
| int64_t FirstKeyVal = getNumericKey(Index, IndexRows[0]); | |
| IsContiguous = true; | |
| for (const auto &[Idx, IndexRow] : enumerate(IndexRows)) { | |
| if (getNumericKey(Index, IndexRow) != FirstKeyVal + (int64_t)Idx) { | |
| IsContiguous = false; | |
| break; | |
| } | |
| } | |
| } | |
| if (Index.EarlyOut || IsContiguous) { | |
| const GenericField &Field = Index.Fields[0]; | |
| std::string FirstRepr = primaryRepresentation( | |
| Index.Loc, Field, IndexRows[0]->getValueInit(Field.Name)); | |
| std::string LastRepr = primaryRepresentation( | |
| Index.Loc, Field, IndexRows.back()->getValueInit(Field.Name)); | |
| std::string TS = | |
| searchableFieldType(Table, Index, Field, TypeInStaticStruct); | |
| OS << " if ((" << TS << ")" << Field.Name << " != std::clamp<" << TS | |
| << ">(" << Field.Name << ", " << FirstRepr << ", " << LastRepr << "))\n"; | |
| OS << " return nullptr;\n\n"; | |
| if (IsContiguous && !Index.EarlyOut) { | |
| OS << " auto Table = ArrayRef(" << IndexName << ");\n"; | |
| OS << " size_t Idx = " << Field.Name << " - " << FirstRepr << ";\n"; | |
| OS << " return "; | |
| if (IsPrimary) | |
| OS << "&Table[Idx]"; | |
| else | |
| OS << "&" << Table.Name << "[Table[Idx]._index]"; | |
| OS << ";\n"; | |
| OS << "}\n"; | |
| return; | |
| } | |
| } | |
| OS << " struct KeyType {\n"; | |
| for (const auto &Field : Index.Fields) { | |
| OS << " " << searchableFieldType(Table, Index, Field, TypeInTempStruct) | |
| << " " << Field.Name << ";\n"; | |
| } | |
| OS << " };\n"; | |
| OS << " KeyType Key = {"; | |
| ListSeparator LS; | |
| for (const auto &Field : Index.Fields) { | |
| OS << LS << Field.Name; | |
| if (isa<StringRecTy>(Field.RecType)) { | |
| OS << ".upper()"; | |
| if (IsPrimary) | |
| PrintFatalError(Index.Loc, | |
| Twine("In table '") + Table.Name + | |
| "', use a secondary lookup method for " | |
| "case-insensitive comparison of field '" + | |
| Field.Name + "'"); | |
| } | |
| } | |
| OS << "};\n"; | |
| OS << " struct Comp {\n"; | |
| OS << " bool operator()(const " << IndexTypeName | |
| << " &LHS, const KeyType &RHS) const {\n"; | |
| auto emitComparator = [&](bool LHSIsKey, bool RHSIsKey) { | |
| for (const auto &Field : Index.Fields) { | |
| if (isa<StringRecTy>(Field.RecType)) { | |
| std::string LHSVar = "LHS" + Field.Name + "Str"; | |
| std::string RHSVar = "RHS" + Field.Name + "Str"; | |
| std::string CmpVar = "Cmp" + Field.Name; | |
| if (LHSIsKey) | |
| OS << " StringRef " << LHSVar << " = LHS." << Field.Name | |
| << ";\n"; | |
| else | |
| OS << " StringRef " << LHSVar << " = " << Table.Name | |
| << "Strings[LHS." << Field.Name << "];\n"; | |
| if (RHSIsKey) | |
| OS << " StringRef " << RHSVar << " = RHS." << Field.Name | |
| << ";\n"; | |
| else | |
| OS << " StringRef " << RHSVar << " = " << Table.Name | |
| << "Strings[RHS." << Field.Name << "];\n"; | |
| OS << " int " << CmpVar << " = " << LHSVar << ".compare(" << RHSVar | |
| << ");\n"; | |
| OS << " if (" << CmpVar << " < 0) return true;\n"; | |
| OS << " if (" << CmpVar << " > 0) return false;\n"; | |
| } else if (Field.Enum) { | |
| // Explicitly cast to unsigned, because the signedness of enums is | |
| // compiler-dependent. | |
| OS << " if ((unsigned)LHS." << Field.Name << " < (unsigned)RHS." | |
| << Field.Name << ")\n"; | |
| OS << " return true;\n"; | |
| OS << " if ((unsigned)LHS." << Field.Name << " > (unsigned)RHS." | |
| << Field.Name << ")\n"; | |
| OS << " return false;\n"; | |
| } else { | |
| OS << " if (LHS." << Field.Name << " < RHS." << Field.Name | |
| << ")\n"; | |
| OS << " return true;\n"; | |
| OS << " if (LHS." << Field.Name << " > RHS." << Field.Name | |
| << ")\n"; | |
| OS << " return false;\n"; | |
| } | |
| } | |
| OS << " return false;\n"; | |
| OS << " }\n"; | |
| }; | |
| emitComparator(false, true); | |
| bool ShouldReturnRange = Index.ReturnRange; | |
| if (ShouldReturnRange) { | |
| OS << " bool operator()(const KeyType &LHS, const " << IndexTypeName | |
| << " &RHS) const {\n"; | |
| emitComparator(true, false); | |
| } | |
| OS << " };\n"; | |
| OS << " auto Table = ArrayRef(" << IndexName << ");\n"; | |
| if (ShouldReturnRange) | |
| OS << " auto It = std::equal_range(Table.begin(), Table.end(), Key, "; | |
| else | |
| OS << " auto Idx = std::lower_bound(Table.begin(), Table.end(), Key, "; | |
| OS << "Comp());\n"; | |
| if (!ShouldReturnRange) { | |
| OS << " if (Idx == Table.end()"; | |
| for (const auto &Field : Index.Fields) { | |
| OS << " ||\n Key." << Field.Name << " != "; | |
| if (isa<StringRecTy>(Field.RecType)) | |
| OS << Table.Name << "Strings[Idx->" << Field.Name << "]"; | |
| else | |
| OS << "Idx->" << Field.Name; | |
| } | |
| } | |
| if (ShouldReturnRange) { | |
| OS << " return llvm::make_range(It.first, It.second);\n"; | |
| } else if (IsPrimary) { | |
| OS << ")\n return nullptr;\n\n"; | |
| OS << " return &*Idx;\n"; | |
| } else { | |
| OS << ")\n return nullptr;\n\n"; | |
| OS << " return &" << Table.Name << "[Idx->_index];\n"; | |
| } | |
| OS << "}\n"; | |
| } | |
| void SearchableTableEmitter::emitLookupDeclaration(const GenericTable &Table, | |
| const SearchIndex &Index, | |
| raw_ostream &OS) { | |
| if (Index.ReturnRange) | |
| OS << "llvm::iterator_range<const " << Table.CppTypeName << " *> "; | |
| else | |
| OS << "const " << Table.CppTypeName << " *"; | |
| OS << Index.Name << "("; | |
| ListSeparator LS; | |
| for (const auto &Field : Index.Fields) | |
| OS << LS << searchableFieldType(Table, Index, Field, TypeInArgument) << " " | |
| << Field.Name; | |
| OS << ")"; | |
| } | |
| void SearchableTableEmitter::emitGenericTable(const GenericTable &Table, | |
| raw_ostream &OS) { | |
| emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_DECL").str(), OS); | |
| // Emit the declarations for the functions that will perform lookup. | |
| if (Table.PrimaryKey) { | |
| emitLookupDeclaration(Table, *Table.PrimaryKey, OS); | |
| OS << ";\n"; | |
| } | |
| for (const auto &Index : Table.Indices) { | |
| emitLookupDeclaration(Table, *Index, OS); | |
| OS << ";\n"; | |
| } | |
| bool HasStrings = false; | |
| for (const auto &Field : Table.Fields) | |
| HasStrings |= isa<StringRecTy>(Field.RecType); | |
| if (HasStrings) | |
| OS << "StringRef get" << Table.CppTypeName << "Str(StringTable::Offset);\n"; | |
| OS << "#endif\n\n"; | |
| emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_IMPL").str(), OS); | |
| StringToOffsetTable StrTab; | |
| SmallVector<const Record *, 0> SparseEntries; | |
| ArrayRef<const Record *> Entries; | |
| unsigned DirectLookupSlots = 0; | |
| if (Table.AllowSparseTable) { | |
| const auto *KeyBits = cast<BitsRecTy>(Table.PrimaryKey->Fields[0].RecType); | |
| DirectLookupSlots = 1u << KeyBits->getNumBits(); | |
| SparseEntries.resize(DirectLookupSlots); | |
| StringRef KeyFieldName = Table.PrimaryKey->Fields[0].Name; | |
| for (const Record *Entry : Table.Entries) { | |
| uint64_t Key = static_cast<uint64_t>(getInt(Entry, KeyFieldName)); | |
| assert(Key < DirectLookupSlots && "key exceeds table size"); | |
| if (SparseEntries[Key]) | |
| PrintFatalError(Entry, Twine("In table '") + Table.Name + | |
| "', duplicate primary key value " + | |
| Twine(Key)); | |
| SparseEntries[Key] = Entry; | |
| } | |
| Entries = SparseEntries; | |
| } else { | |
| Entries = Table.Entries; | |
| } | |
| // The primary data table contains all the fields defined for this map. | |
| OS << "constexpr " << Table.CppTypeName << " " << Table.Name << "[] = {\n"; | |
| for (const auto &[Idx, Entry] : enumerate(Entries)) { | |
| OS << " { "; | |
| ListSeparator LS; | |
| if (Entry) { | |
| for (const auto &Field : Table.Fields) { | |
| OS << LS; | |
| const Init *Value = Entry->getValueInit(Field.Name); | |
| if (const auto *SI = dyn_cast<StringInit>(Value); | |
| SI && !Field.IsCode && !SI->hasCodeFormat()) { | |
| OS << StrTab.GetOrAddStringOffset(Value->getAsUnquotedString()) | |
| << " /* " << primaryRepresentation(Table.Locs[0], Field, Value) | |
| << " */"; | |
| } else { | |
| OS << primaryRepresentation(Table.Locs[0], Field, Value); | |
| } | |
| } | |
| } else if (Table.AllowSparseTable) { | |
| // For empty rows emit a sentinel value as a key, so we can return null | |
| // during lookup. Value is constructed such that LookupKey != KeyField. | |
| for (const auto &Field : Table.Fields) { | |
| OS << LS; | |
| if (Field.Name == Table.PrimaryKey->Fields[0].Name) | |
| OS << "0x" << (Idx == 0 ? 1 : 0); | |
| else | |
| OS << "{}"; | |
| } | |
| } | |
| OS << " }, // " << Idx << "\n"; | |
| } | |
| OS << " };\n"; | |
| // Emit into string first so we can put the string table before the function. | |
| // The lookup function might add more strings. | |
| std::string LookupFunction; | |
| raw_string_ostream LFOS(LookupFunction); | |
| if (Table.AllowSparseTable) { | |
| LFOS << "\n"; | |
| emitLookupDeclaration(Table, *Table.PrimaryKey, LFOS); | |
| LFOS << " {\n"; | |
| const GenericField &Field = Table.PrimaryKey->Fields[0]; | |
| LFOS << " if (" << Field.Name << " >= " << DirectLookupSlots << ")\n"; | |
| LFOS << " return nullptr;\n"; | |
| LFOS << " const auto *Entry = &" << Table.Name << "[" << Field.Name | |
| << "];\n"; | |
| LFOS << " return Entry->" << Field.Name << " == " << Field.Name | |
| << " ? Entry : nullptr;\n"; | |
| LFOS << "}\n"; | |
| } else if (Table.PrimaryKey) { | |
| // Indexes are sorted "{ Thing, PrimaryIdx }" arrays, so that a binary | |
| // search can be performed by "Thing". | |
| emitLookupFunction(Table, *Table.PrimaryKey, /*IsPrimary=*/true, StrTab, | |
| LFOS); | |
| } | |
| for (const auto &Index : Table.Indices) | |
| emitLookupFunction(Table, *Index, /*IsPrimary=*/false, StrTab, LFOS); | |
| if (HasStrings) { | |
| StrTab.EmitStringTableDef(OS, Table.Name + Twine("Strings")); | |
| OS << "\nStringRef get" << Table.CppTypeName | |
| << "Str(StringTable::Offset Offset) {\n"; | |
| OS << " return " << Table.Name << "Strings[Offset];\n"; | |
| OS << "}\n"; | |
| } | |
| OS << LookupFunction; | |
| OS << "#endif\n\n"; | |
| } | |
| bool SearchableTableEmitter::parseFieldType(GenericField &Field, | |
| const Init *TypeOf) { | |
| auto Type = dyn_cast<StringInit>(TypeOf); | |
| if (!Type) | |
| return false; | |
| StringRef TypeStr = Type->getValue(); | |
| if (TypeStr == "code") { | |
| Field.IsCode = true; | |
| return true; | |
| } | |
| if (const Record *TypeRec = Records.getDef(TypeStr)) { | |
| if (TypeRec->isSubClassOf("GenericEnum")) { | |
| Field.Enum = EnumMap[TypeRec]; | |
| Field.RecType = RecordRecTy::get(Field.Enum->Class); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| std::unique_ptr<SearchIndex> SearchableTableEmitter::parseSearchIndex( | |
| GenericTable &Table, const RecordVal *KeyRecVal, StringRef Name, | |
| ArrayRef<StringRef> Key, bool EarlyOut, bool ReturnRange) { | |
| auto Index = std::make_unique<SearchIndex>(); | |
| Index->Name = Name.str(); | |
| Index->Loc = KeyRecVal->getLoc(); | |
| Index->EarlyOut = EarlyOut; | |
| Index->ReturnRange = ReturnRange; | |
| for (const auto &FieldName : Key) { | |
| const GenericField *Field = Table.getFieldByName(FieldName); | |
| if (!Field) | |
| PrintFatalError( | |
| KeyRecVal, | |
| Twine("In table '") + Table.Name + | |
| "', 'PrimaryKey' or 'Key' refers to nonexistent field '" + | |
| FieldName + "'"); | |
| Index->Fields.push_back(*Field); | |
| } | |
| if (EarlyOut && isa<StringRecTy>(Index->Fields[0].RecType)) { | |
| PrintFatalError( | |
| KeyRecVal, Twine("In lookup method '") + Name + "', early-out is not " + | |
| "supported for a first key field of type string"); | |
| } | |
| return Index; | |
| } | |
| void SearchableTableEmitter::collectEnumEntries( | |
| GenericEnum &Enum, StringRef NameField, StringRef ValueField, | |
| ArrayRef<const Record *> Items) { | |
| Enum.Entries.reserve(Items.size()); | |
| for (const Record *EntryRec : Items) { | |
| StringRef Name = NameField.empty() ? EntryRec->getName() | |
| : EntryRec->getValueAsString(NameField); | |
| int64_t Value = ValueField.empty() ? 0 : getInt(EntryRec, ValueField); | |
| Enum.Entries.try_emplace(EntryRec, Name, Value); | |
| } | |
| // If no values are provided for enums, assign values in the order of sorted | |
| // enum names. | |
| if (ValueField.empty()) { | |
| // Copy the map entries for sorting and clear the map. | |
| auto SavedEntries = Enum.Entries.takeVector(); | |
| using MapVectorEntryTy = std::pair<const Record *, GenericEnum::Entry>; | |
| llvm::stable_sort(SavedEntries, [](const MapVectorEntryTy &LHS, | |
| const MapVectorEntryTy &RHS) { | |
| return LHS.second.Name < RHS.second.Name; | |
| }); | |
| // Repopulate entries using the new sorted order. | |
| for (auto [Idx, Entry] : enumerate(SavedEntries)) | |
| Enum.Entries.try_emplace(Entry.first, Entry.second.Name, Idx); | |
| } | |
| } | |
| void SearchableTableEmitter::collectTableEntries( | |
| GenericTable &Table, ArrayRef<const Record *> Items) { | |
| if (Items.empty()) | |
| PrintFatalError(Table.Locs, | |
| Twine("Table '") + Table.Name + "' has no entries"); | |
| for (auto *EntryRec : Items) { | |
| for (auto &Field : Table.Fields) { | |
| auto TI = dyn_cast<TypedInit>(EntryRec->getValueInit(Field.Name)); | |
| if (!TI || !TI->isComplete()) { | |
| PrintFatalError(EntryRec, Twine("Record '") + EntryRec->getName() + | |
| "' for table '" + Table.Name + | |
| "' is missing field '" + Field.Name + | |
| "'"); | |
| } | |
| if (!Field.RecType) { | |
| Field.RecType = TI->getType(); | |
| } else { | |
| const RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); | |
| if (!Ty) | |
| PrintFatalError(EntryRec->getValue(Field.Name), | |
| Twine("Field '") + Field.Name + "' of table '" + | |
| Table.Name + "' entry has incompatible type: " + | |
| TI->getType()->getAsString() + " vs. " + | |
| Field.RecType->getAsString()); | |
| Field.RecType = Ty; | |
| } | |
| } | |
| Table.Entries.push_back(EntryRec); // Add record to table's record list. | |
| } | |
| const Record *IntrinsicClass = Records.getClass("Intrinsic"); | |
| const Record *InstructionClass = Records.getClass("Instruction"); | |
| for (auto &Field : Table.Fields) { | |
| if (!Field.RecType) | |
| PrintFatalError(Twine("Cannot determine type of field '") + Field.Name + | |
| "' in table '" + Table.Name + "'. Maybe it is not used?"); | |
| if (auto RecordTy = dyn_cast<RecordRecTy>(Field.RecType)) { | |
| if (IntrinsicClass && RecordTy->isSubClassOf(IntrinsicClass)) | |
| Field.IsIntrinsic = true; | |
| else if (InstructionClass && RecordTy->isSubClassOf(InstructionClass)) | |
| Field.IsInstruction = true; | |
| } | |
| } | |
| SearchIndex Idx; | |
| llvm::append_range(Idx.Fields, Table.Fields); | |
| llvm::sort(Table.Entries, [&](const Record *LHS, const Record *RHS) { | |
| return compareBy(LHS, RHS, Idx); | |
| }); | |
| } | |
| static bool canUseSparseTable(const Record *TableRec, | |
| const std::unique_ptr<GenericTable> &Table) { | |
| if (TableRec->getValueAsBit("DisallowSparseTable")) | |
| return false; | |
| // Sparse tables are only supported with a single primary key. | |
| if (Table->PrimaryKey->Fields.size() != 1) | |
| return false; | |
| const auto *KeyBits = | |
| dyn_cast<BitsRecTy>(Table->PrimaryKey->Fields[0].RecType); | |
| // Sparse tables only support `bits` key. | |
| if (!KeyBits) | |
| return false; | |
| // Sparse tables are not compatible with PrimaryKeyReturnRange. | |
| if (TableRec->getValueAsBit("PrimaryKeyReturnRange")) | |
| return false; | |
| // Only support tables up to 4k in size. | |
| constexpr unsigned MaxKeyBits = 12; | |
| if (KeyBits->getNumBits() > MaxKeyBits) | |
| return false; | |
| return true; | |
| } | |
| void SearchableTableEmitter::run(raw_ostream &OS) { | |
| // Emit tables in a deterministic order to avoid needless rebuilds. | |
| SmallVector<std::unique_ptr<GenericTable>, 4> Tables; | |
| DenseMap<const Record *, GenericTable *> TableMap; | |
| bool NeedsTarget = | |
| !Records.getAllDerivedDefinitionsIfDefined("Instruction").empty() || | |
| !Records.getAllDerivedDefinitionsIfDefined("Intrinsic").empty(); | |
| if (NeedsTarget) | |
| Target = std::make_unique<CodeGenTarget>(Records); | |
| // Collect all definitions first. | |
| for (const auto *EnumRec : Records.getAllDerivedDefinitions("GenericEnum")) { | |
| StringRef NameField; | |
| if (!EnumRec->isValueUnset("NameField")) | |
| NameField = EnumRec->getValueAsString("NameField"); | |
| StringRef ValueField; | |
| if (!EnumRec->isValueUnset("ValueField")) | |
| ValueField = EnumRec->getValueAsString("ValueField"); | |
| auto Enum = std::make_unique<GenericEnum>(); | |
| Enum->Name = EnumRec->getName().str(); | |
| Enum->PreprocessorGuard = EnumRec->getName().str(); | |
| StringRef FilterClass = EnumRec->getValueAsString("FilterClass"); | |
| Enum->Class = Records.getClass(FilterClass); | |
| if (!Enum->Class) | |
| PrintFatalError(EnumRec->getValue("FilterClass"), | |
| Twine("Enum FilterClass '") + FilterClass + | |
| "' does not exist"); | |
| if (!EnumRec->isValueUnset("UnderlyingType")) | |
| Enum->UnderlyingType = EnumRec->getValueAsString("UnderlyingType"); | |
| collectEnumEntries(*Enum, NameField, ValueField, | |
| Records.getAllDerivedDefinitions(FilterClass)); | |
| EnumMap.try_emplace(EnumRec, Enum.get()); | |
| Enums.emplace_back(std::move(Enum)); | |
| } | |
| for (const auto *TableRec : | |
| Records.getAllDerivedDefinitions("GenericTable")) { | |
| auto Table = std::make_unique<GenericTable>(); | |
| Table->Name = TableRec->getName().str(); | |
| Table->Locs = TableRec->getLoc(); | |
| Table->PreprocessorGuard = TableRec->getName().str(); | |
| Table->CppTypeName = TableRec->getValueAsString("CppTypeName").str(); | |
| std::vector<StringRef> Fields = TableRec->getValueAsListOfStrings("Fields"); | |
| for (const auto &FieldName : Fields) { | |
| Table->Fields.emplace_back(FieldName); // Construct a GenericField. | |
| if (auto TypeOfRecordVal = | |
| TableRec->getValue(("TypeOf_" + FieldName).str())) { | |
| if (!parseFieldType(Table->Fields.back(), | |
| TypeOfRecordVal->getValue())) { | |
| PrintError(TypeOfRecordVal, | |
| Twine("Table '") + Table->Name + "' has invalid 'TypeOf_" + | |
| FieldName + | |
| "': " + TypeOfRecordVal->getValue()->getAsString()); | |
| PrintFatalNote("The 'TypeOf_xxx' field must be a string naming a " | |
| "GenericEnum record, or \"code\""); | |
| } | |
| } | |
| } | |
| StringRef FilterClass = TableRec->getValueAsString("FilterClass"); | |
| if (!Records.getClass(FilterClass)) | |
| PrintFatalError(TableRec->getValue("FilterClass"), | |
| Twine("Table FilterClass '") + FilterClass + | |
| "' does not exist"); | |
| const RecordVal *FilterClassFieldVal = | |
| TableRec->getValue("FilterClassField"); | |
| std::vector<const Record *> Definitions = | |
| Records.getAllDerivedDefinitions(FilterClass); | |
| if (auto *FilterClassFieldInit = | |
| dyn_cast<StringInit>(FilterClassFieldVal->getValue())) { | |
| StringRef FilterClassField = FilterClassFieldInit->getValue(); | |
| llvm::erase_if(Definitions, [&](const Record *R) { | |
| const RecordVal *Filter = R->getValue(FilterClassField); | |
| if (auto *BitV = dyn_cast<BitInit>(Filter->getValue())) | |
| return !BitV->getValue(); | |
| PrintFatalError(Filter, Twine("FilterClassField '") + FilterClass + | |
| "' should be a bit value"); | |
| return true; | |
| }); | |
| } | |
| collectTableEntries(*Table, Definitions); | |
| if (!TableRec->isValueUnset("PrimaryKey")) { | |
| Table->PrimaryKey = | |
| parseSearchIndex(*Table, TableRec->getValue("PrimaryKey"), | |
| TableRec->getValueAsString("PrimaryKeyName"), | |
| TableRec->getValueAsListOfStrings("PrimaryKey"), | |
| TableRec->getValueAsBit("PrimaryKeyEarlyOut"), | |
| TableRec->getValueAsBit("PrimaryKeyReturnRange")); | |
| llvm::stable_sort(Table->Entries, | |
| [&](const Record *LHS, const Record *RHS) { | |
| return compareBy(LHS, RHS, *Table->PrimaryKey); | |
| }); | |
| Table->AllowSparseTable = canUseSparseTable(TableRec, Table); | |
| } | |
| TableMap.try_emplace(TableRec, Table.get()); | |
| Tables.emplace_back(std::move(Table)); | |
| } | |
| for (const Record *IndexRec : | |
| Records.getAllDerivedDefinitions("SearchIndex")) { | |
| const Record *TableRec = IndexRec->getValueAsDef("Table"); | |
| auto It = TableMap.find(TableRec); | |
| if (It == TableMap.end()) | |
| PrintFatalError(IndexRec->getValue("Table"), | |
| Twine("SearchIndex '") + IndexRec->getName() + | |
| "' refers to nonexistent table '" + | |
| TableRec->getName()); | |
| GenericTable &Table = *It->second; | |
| Table.Indices.push_back(parseSearchIndex( | |
| Table, IndexRec->getValue("Key"), IndexRec->getName(), | |
| IndexRec->getValueAsListOfStrings("Key"), | |
| IndexRec->getValueAsBit("EarlyOut"), /*ReturnRange*/ false)); | |
| // Sparse tables with secondary search indices are not supported. | |
| Table.AllowSparseTable = false; | |
| } | |
| // Translate legacy tables. | |
| const Record *SearchableTable = Records.getClass("SearchableTable"); | |
| for (auto &NameRec : Records.getClasses()) { | |
| const Record *Class = NameRec.second.get(); | |
| if (Class->getDirectSuperClasses().size() != 1 || | |
| !Class->isSubClassOf(SearchableTable)) | |
| continue; | |
| StringRef TableName = Class->getName(); | |
| ArrayRef<const Record *> Items = | |
| Records.getAllDerivedDefinitions(TableName); | |
| if (!Class->isValueUnset("EnumNameField")) { | |
| StringRef NameField = Class->getValueAsString("EnumNameField"); | |
| StringRef ValueField; | |
| if (!Class->isValueUnset("EnumValueField")) | |
| ValueField = Class->getValueAsString("EnumValueField"); | |
| auto Enum = std::make_unique<GenericEnum>(); | |
| Enum->Name = (Twine(Class->getName()) + "Values").str(); | |
| Enum->PreprocessorGuard = Class->getName().upper(); | |
| Enum->Class = Class; | |
| collectEnumEntries(*Enum, NameField, ValueField, Items); | |
| Enums.emplace_back(std::move(Enum)); | |
| } | |
| auto Table = std::make_unique<GenericTable>(); | |
| Table->Name = (Twine(Class->getName()) + "sList").str(); | |
| Table->Locs = Class->getLoc(); | |
| Table->PreprocessorGuard = Class->getName().upper(); | |
| Table->CppTypeName = Class->getName().str(); | |
| for (const RecordVal &Field : Class->getValues()) { | |
| std::string FieldName = Field.getName().str(); | |
| // Skip uninteresting fields: either special to us, or injected | |
| // template parameters (if they contain a ':'). | |
| if (FieldName.find(':') != std::string::npos || | |
| FieldName == "SearchableFields" || FieldName == "EnumNameField" || | |
| FieldName == "EnumValueField") | |
| continue; | |
| Table->Fields.emplace_back(FieldName); | |
| } | |
| collectTableEntries(*Table, Items); | |
| for (const auto &Field : | |
| Class->getValueAsListOfStrings("SearchableFields")) { | |
| std::string Name = | |
| (Twine("lookup") + Table->CppTypeName + "By" + Field).str(); | |
| Table->Indices.push_back( | |
| parseSearchIndex(*Table, Class->getValue(Field), Name, {Field}, | |
| /*EarlyOut*/ false, /*ReturnRange*/ false)); | |
| } | |
| Tables.emplace_back(std::move(Table)); | |
| } | |
| // Emit everything. | |
| for (const auto &Enum : Enums) | |
| emitGenericEnum(*Enum, OS); | |
| for (const auto &Table : Tables) | |
| emitGenericTable(*Table, OS); | |
| // Put all #undefs last, to allow multiple sections guarded by the same | |
| // define. | |
| for (const auto &Guard : PreprocessorGuards) | |
| OS << "#undef " << Guard << "\n"; | |
| } | |
| static TableGen::Emitter::OptClass<SearchableTableEmitter> | |
| X("gen-searchable-tables", "Generate generic binary-searchable table"); |