[llvm] [TableGen] Add support for sparse direct-lookup tables (PR #201158)
Igor Wodiany via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 8 09:27:14 PDT 2026
https://github.com/IgWod updated https://github.com/llvm/llvm-project/pull/201158
>From 3261712dc0948c69de32e955083c77717d660437 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at amd.com>
Date: Fri, 29 May 2026 16:32:52 +0100
Subject: [PATCH 1/4] [TableGen] Add support for sparse direct-lookup tables
This change is motivated by the recent AMDGPU patch (#200241)
where a sparse direct-lookup table is generated manually
to improve VOPD eligiblity lookup. The `GenericTable` only
generate a direct look-up for continues tables, defaulting to
binary search for non-contngious spaces. This patch adds a
`SparseLookupTable` that allows to trade-off table size with
look up speed by generating spare tables. For example the
following sparse table:
```
def VOPDYSource : SparseTableSource {
let FilterClass = "VOPDY";
let KeyField = "VOPDXYKey";
let Fields = ["", "IsY"];
}
def VOPDXSource : SparseTableSource {
let FilterClass = "VOPDX";
let KeyField = "VOPDXYKey";
let Fields = ["IsX", ""];
}
def VOPDXYTable : SparseTable {
let CppTypeName = "CanBeVOPD";
let KeyBits = 11;
let ResultFields = ["X", "Y"];
let Sources = [VOPDXSource, VOPDYSource];
let LookupFuncName = "getCanBeVOPDXY";
}
```
Generates following C++ code:
```c++
const CanBeVOPD *getCanBeVOPDXY(unsigned Key);
constexpr CanBeVOPD VOPDXYTable[] = {
{ }, // 0
{ }, // 1
{ }, // 2
{ }, // 3
{ }, // 4
{ }, // 5
{ }, // 6
{ }, // 7
{ }, // 8
{ }, // 9
{ }, // 10
{ }, // 11
{ }, // 12
{ }, // 13
{ }, // 14
{ }, // 15
{ }, // 16
{ }, // 17
{ }, // 18
{ }, // 19
{ true, true }, // 20
{ }, // 21
{ true, true }, // 22
{ }, // 23
{ true, true }, // 24
{ }, // 25
{ true, true }, // 26
{ true, true }, // 27
{ true, true }, // 28
{ true, true }, // 29
{ }, // 30
{ }, // 31
{ }, // 32
...
{ }, // 2047
};
const CanBeVOPD *getCanBeVOPDXY(unsigned Key) {
assert(Key < 2048 && "getCanBeVOPDXY: key out of range");
return &VOPDXYTable[Key];
}
```
---
llvm/include/llvm/TableGen/SearchableTable.td | 70 +++++++++
.../utils/TableGen/SearchableTableEmitter.cpp | 146 ++++++++++++++++++
2 files changed, 216 insertions(+)
diff --git a/llvm/include/llvm/TableGen/SearchableTable.td b/llvm/include/llvm/TableGen/SearchableTable.td
index 71b837731c440..a928872c4e2dd 100644
--- a/llvm/include/llvm/TableGen/SearchableTable.td
+++ b/llvm/include/llvm/TableGen/SearchableTable.td
@@ -148,6 +148,76 @@ class SearchIndex {
bit EarlyOut = false;
}
+// Describes one filter class contribution to a `SparseTable`.
+//
+// `Fields` is a list whose length must equal to the length of `ResultFields`
+// on the owning `SparseTable`. Each entry names a field on the FilterClass record
+// to read; an empty entry leaves that result member at its C++ default (`{}`).
+//
+// For example:
+//
+// def VOPDYSource : SparseTableSource {
+// let FilterClass = "VOPDY";
+// let KeyField = "VOPDXYKey";
+// let Fields = ["", "IsY"];
+// }
+//
+// def VOPDXSource : SparseTableSource {
+// let FilterClass = "VOPDX";
+// let KeyField = "VOPDXYKey";
+// let Fields = ["IsX", ""];
+// }
+//
+class SparseTableSource {
+ // Name of a class. The table will have one entry for each record that
+ // derives from that class.
+ string FilterClass;
+
+ // Name of the pre-packed key field (must be a bits<N> field on `FilterClass`).
+ // The user is responsible for packing multiple logical fields into this one
+ // field.
+ string KeyField;
+
+ // Parallel to `ResultFields` on the owning `SparseTable`. Non-empty entry
+ // contains a name of a field on the `FilterClass` record to read.
+ list<string> Fields = [];
+}
+
+// Generate a sparse directly-indexed lookup array of size 2 ^ `KeyBits`.
+//
+// Unlike `GenericTable` whose direct-lookup path requires contiguous key values,
+// `SparseTable` supports sparse key spaces.
+//
+// For example:
+//
+// def VOPDXYTable : SparseTable {
+// let CppTypeName = "CanBeVOPD";
+// let KeyBits = 11;
+// let ResultFields = ["X", "Y"];
+// let Sources = [VOPDXSource, VOPDYSource];
+// let LookupFuncName = "getCanBeVOPDXY";
+// }
+//
+class SparseTable {
+ // Name of the C++ struct/class type that holds table entries. The
+ // declaration of this type is not generated automatically.
+ string CppTypeName;
+
+ // The emitted array has 2 ^ `KeyBits` elements. Must match the bit-width of the
+ // `KeyField` on every contributing `SparseTableSource`.
+ int KeyBits;
+
+ // Ordered list of result struct member names. The `Fields` list on
+ // each `SparseTableSource` aligns to this ordering.
+ list<string> ResultFields;
+
+ // Source classes that populate the table.
+ list<SparseTableSource> Sources;
+
+ // Name of the generated lookup function.
+ string LookupFuncName;
+}
+
// Legacy table type with integrated enum.
class SearchableTable {
list<string> SearchableFields;
diff --git a/llvm/utils/TableGen/SearchableTableEmitter.cpp b/llvm/utils/TableGen/SearchableTableEmitter.cpp
index e100682bbcec3..c77a0a625779e 100644
--- a/llvm/utils/TableGen/SearchableTableEmitter.cpp
+++ b/llvm/utils/TableGen/SearchableTableEmitter.cpp
@@ -84,6 +84,23 @@ struct SearchIndex {
bool ReturnRange = false;
};
+struct SparseTableSource {
+ std::string FilterClass;
+ std::string KeyField;
+ SmallVector<std::string, 2> Fields;
+};
+
+struct SparseTable {
+ std::string Name;
+ ArrayRef<SMLoc> Locs; // Source locations from the Record instance.
+ std::string PreprocessorGuard;
+ std::string CppTypeName;
+ unsigned KeyBits;
+ SmallVector<std::string, 2> ResultFields;
+ SmallVector<SparseTableSource, 2> Sources;
+ std::string LookupFuncName;
+};
+
struct GenericTable {
std::string Name;
ArrayRef<SMLoc> Locs; // Source locations from the Record instance.
@@ -218,6 +235,9 @@ class SearchableTableEmitter {
StringRef ValueField, ArrayRef<const Record *> Items);
void collectTableEntries(GenericTable &Table, ArrayRef<const Record *> Items);
int64_t getNumericKey(const SearchIndex &Index, const Record *Rec);
+
+ std::unique_ptr<SparseTable> parseSparseTable(const Record *Rec);
+ void emitSparseTable(const SparseTable &Table, raw_ostream &OS);
};
} // End anonymous namespace.
@@ -736,9 +756,128 @@ void SearchableTableEmitter::collectTableEntries(
});
}
+std::unique_ptr<SparseTable>
+SearchableTableEmitter::parseSparseTable(const Record *Rec) {
+ auto Table = std::make_unique<SparseTable>();
+ Table->Name = Rec->getName().str();
+ Table->Locs = Rec->getLoc();
+ Table->PreprocessorGuard = Rec->getName().str();
+ Table->CppTypeName = Rec->getValueAsString("CppTypeName").str();
+ int64_t KeyBitsRaw = Rec->getValueAsInt("KeyBits");
+ if (KeyBitsRaw <= 0)
+ PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
+ "' has non-positive KeyBits");
+ Table->KeyBits = static_cast<unsigned>(KeyBitsRaw);
+ Table->LookupFuncName = Rec->getValueAsString("LookupFuncName").str();
+
+ for (StringRef RF : Rec->getValueAsListOfStrings("ResultFields"))
+ Table->ResultFields.push_back(RF.str());
+
+ if (Table->ResultFields.empty())
+ PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
+ "' has empty 'ResultFields'");
+
+ for (const Record *SrcRec : Rec->getValueAsListOfDefs("Sources")) {
+ SparseTableSource Src;
+ Src.FilterClass = SrcRec->getValueAsString("FilterClass").str();
+ Src.KeyField = SrcRec->getValueAsString("KeyField").str();
+
+ if (!Records.getClass(Src.FilterClass))
+ PrintFatalError(SrcRec->getValue("FilterClass"),
+ Twine("SparseTableSource FilterClass '") +
+ Src.FilterClass + "' does not exist");
+
+ for (StringRef SF : SrcRec->getValueAsListOfStrings("Fields"))
+ Src.Fields.push_back(SF.str());
+
+ if (Src.Fields.empty())
+ PrintFatalError(SrcRec, Twine("SparseTableSource for table '") +
+ Table->Name + "' has empty 'Fields'");
+ if (Src.Fields.size() != Table->ResultFields.size())
+ PrintFatalError(SrcRec, Twine("SparseTableSource for table '") +
+ Table->Name +
+ "', 'Fields' length must match "
+ "'ResultFields' length");
+
+ Table->Sources.push_back(std::move(Src));
+ }
+
+ if (Table->Sources.empty())
+ PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
+ "' has empty 'Sources'");
+
+ return Table;
+}
+
+void SearchableTableEmitter::emitSparseTable(const SparseTable &Table,
+ raw_ostream &OS) {
+ unsigned NumRows = 1u << Table.KeyBits;
+ unsigned NumFields = Table.ResultFields.size();
+ SmallVector<SmallVector<std::string, 2>> Slots(
+ NumRows, SmallVector<std::string, 2>(NumFields));
+
+ for (const SparseTableSource &Src : Table.Sources) {
+ for (const Record *Entry :
+ Records.getAllDerivedDefinitions(Src.FilterClass)) {
+ const Init *KeyInit = Entry->getValueInit(Src.KeyField);
+ uint64_t Key = static_cast<uint64_t>(getAsInt(KeyInit));
+ if (Key >= NumRows)
+ PrintFatalError(Entry, Twine("In SparseTable '") + Table.Name +
+ "', key value " + Twine(Key) +
+ " exceeds 2^KeyBits (" + Twine(NumRows) +
+ ")");
+ for (unsigned I = 0; I < NumFields; ++I) {
+ if (Src.Fields[I].empty())
+ continue;
+ const Init *FieldInit = Entry->getValueInit(Src.Fields[I]);
+ GenericField F(Src.Fields[I]);
+ F.RecType = cast<TypedInit>(FieldInit)->getType();
+ std::string Val = primaryRepresentation(Table.Locs[0], F, FieldInit);
+ if (!Slots[Key][I].empty() && Slots[Key][I] != Val)
+ PrintFatalError(Entry, Twine("In SparseTable '") + Table.Name +
+ "', key value " + Twine(Key) +
+ " has conflicting values for field '" +
+ Src.Fields[I] + "'");
+ Slots[Key][I] = std::move(Val);
+ }
+ }
+ }
+
+ emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_DECL").str(), OS);
+
+ OS << "const " << Table.CppTypeName << " *" << Table.LookupFuncName
+ << "(unsigned Key);\n";
+ OS << "#endif\n\n";
+
+ emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_IMPL").str(), OS);
+
+ OS << "constexpr " << Table.CppTypeName << " " << Table.Name << "[] = {\n";
+ for (unsigned I = 0; I < NumRows; ++I) {
+ OS << " { ";
+ if (llvm::any_of(Slots[I],
+ [](const std::string &V) { return !V.empty(); })) {
+ ListSeparator LS;
+ for (const std::string &Val : Slots[I])
+ OS << LS << (Val.empty() ? "{}" : Val);
+ }
+ OS << " }, // " << I << "\n";
+ }
+ OS << " };\n";
+
+ OS << "const " << Table.CppTypeName << " *" << Table.LookupFuncName
+ << "(unsigned Key) {\n";
+ OS << " assert(Key < " << NumRows << " && \"" << Table.LookupFuncName
+ << ": key out of range\");\n";
+ OS << " return &" << Table.Name << "[Key];\n";
+ OS << "}\n";
+
+ OS << "#endif\n\n";
+}
+
void SearchableTableEmitter::run(raw_ostream &OS) {
// Emit tables in a deterministic order to avoid needless rebuilds.
SmallVector<std::unique_ptr<GenericTable>, 4> Tables;
+ SmallVector<std::unique_ptr<SparseTable>, 4> SparseTables;
DenseMap<const Record *, GenericTable *> TableMap;
bool NeedsTarget =
!Records.getAllDerivedDefinitionsIfDefined("Instruction").empty() ||
@@ -862,6 +1001,10 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
IndexRec->getValueAsBit("EarlyOut"), /*ReturnRange*/ false));
}
+ for (const Record *TableRec :
+ Records.getAllDerivedDefinitionsIfDefined("SparseTable"))
+ SparseTables.push_back(parseSparseTable(TableRec));
+
// Translate legacy tables.
const Record *SearchableTable = Records.getClass("SearchableTable");
for (auto &NameRec : Records.getClasses()) {
@@ -929,6 +1072,9 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
for (const auto &Table : Tables)
emitGenericTable(*Table, OS);
+ for (const auto &Table : SparseTables)
+ emitSparseTable(*Table, OS);
+
// Put all #undefs last, to allow multiple sections guarded by the same
// define.
for (const auto &Guard : PreprocessorGuards)
>From 2fc6170ed0642800c218d4365827518c3747998a Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at amd.com>
Date: Fri, 5 Jun 2026 21:36:59 +0100
Subject: [PATCH 2/4] Extend GenericTable
---
llvm/include/llvm/TableGen/SearchableTable.td | 77 +------
.../utils/TableGen/SearchableTableEmitter.cpp | 218 ++++++------------
2 files changed, 76 insertions(+), 219 deletions(-)
diff --git a/llvm/include/llvm/TableGen/SearchableTable.td b/llvm/include/llvm/TableGen/SearchableTable.td
index a928872c4e2dd..092ac74c982d2 100644
--- a/llvm/include/llvm/TableGen/SearchableTable.td
+++ b/llvm/include/llvm/TableGen/SearchableTable.td
@@ -125,6 +125,13 @@ class GenericTable {
// iterator range of pointers giving the starting and end value of the range.
// e.g. lookupSysRegByEncoding returns multiple CSRs for same encoding.
bit PrimaryKeyReturnRange = false;
+
+ // Opt out of emitting the primary table as a direct-lookup sparse array
+ // indexed by the primary key value.
+ // TODO: Eventually this should default to `false`, so it is up to heuristics
+ // in the emitter to pick the best lookup scheme. It will then be used to
+ // override that logic and force an emission of non-sparse array.
+ bit DisallowSparseTable = true;
}
// Define a record derived from this class to generate an additional search
@@ -148,76 +155,6 @@ class SearchIndex {
bit EarlyOut = false;
}
-// Describes one filter class contribution to a `SparseTable`.
-//
-// `Fields` is a list whose length must equal to the length of `ResultFields`
-// on the owning `SparseTable`. Each entry names a field on the FilterClass record
-// to read; an empty entry leaves that result member at its C++ default (`{}`).
-//
-// For example:
-//
-// def VOPDYSource : SparseTableSource {
-// let FilterClass = "VOPDY";
-// let KeyField = "VOPDXYKey";
-// let Fields = ["", "IsY"];
-// }
-//
-// def VOPDXSource : SparseTableSource {
-// let FilterClass = "VOPDX";
-// let KeyField = "VOPDXYKey";
-// let Fields = ["IsX", ""];
-// }
-//
-class SparseTableSource {
- // Name of a class. The table will have one entry for each record that
- // derives from that class.
- string FilterClass;
-
- // Name of the pre-packed key field (must be a bits<N> field on `FilterClass`).
- // The user is responsible for packing multiple logical fields into this one
- // field.
- string KeyField;
-
- // Parallel to `ResultFields` on the owning `SparseTable`. Non-empty entry
- // contains a name of a field on the `FilterClass` record to read.
- list<string> Fields = [];
-}
-
-// Generate a sparse directly-indexed lookup array of size 2 ^ `KeyBits`.
-//
-// Unlike `GenericTable` whose direct-lookup path requires contiguous key values,
-// `SparseTable` supports sparse key spaces.
-//
-// For example:
-//
-// def VOPDXYTable : SparseTable {
-// let CppTypeName = "CanBeVOPD";
-// let KeyBits = 11;
-// let ResultFields = ["X", "Y"];
-// let Sources = [VOPDXSource, VOPDYSource];
-// let LookupFuncName = "getCanBeVOPDXY";
-// }
-//
-class SparseTable {
- // Name of the C++ struct/class type that holds table entries. The
- // declaration of this type is not generated automatically.
- string CppTypeName;
-
- // The emitted array has 2 ^ `KeyBits` elements. Must match the bit-width of the
- // `KeyField` on every contributing `SparseTableSource`.
- int KeyBits;
-
- // Ordered list of result struct member names. The `Fields` list on
- // each `SparseTableSource` aligns to this ordering.
- list<string> ResultFields;
-
- // Source classes that populate the table.
- list<SparseTableSource> Sources;
-
- // Name of the generated lookup function.
- string LookupFuncName;
-}
-
// Legacy table type with integrated enum.
class SearchableTable {
list<string> SearchableFields;
diff --git a/llvm/utils/TableGen/SearchableTableEmitter.cpp b/llvm/utils/TableGen/SearchableTableEmitter.cpp
index c77a0a625779e..adbca498307e3 100644
--- a/llvm/utils/TableGen/SearchableTableEmitter.cpp
+++ b/llvm/utils/TableGen/SearchableTableEmitter.cpp
@@ -84,23 +84,6 @@ struct SearchIndex {
bool ReturnRange = false;
};
-struct SparseTableSource {
- std::string FilterClass;
- std::string KeyField;
- SmallVector<std::string, 2> Fields;
-};
-
-struct SparseTable {
- std::string Name;
- ArrayRef<SMLoc> Locs; // Source locations from the Record instance.
- std::string PreprocessorGuard;
- std::string CppTypeName;
- unsigned KeyBits;
- SmallVector<std::string, 2> ResultFields;
- SmallVector<SparseTableSource, 2> Sources;
- std::string LookupFuncName;
-};
-
struct GenericTable {
std::string Name;
ArrayRef<SMLoc> Locs; // Source locations from the Record instance.
@@ -112,6 +95,8 @@ struct GenericTable {
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)
@@ -235,9 +220,6 @@ class SearchableTableEmitter {
StringRef ValueField, ArrayRef<const Record *> Items);
void collectTableEntries(GenericTable &Table, ArrayRef<const Record *> Items);
int64_t getNumericKey(const SearchIndex &Index, const Record *Rec);
-
- std::unique_ptr<SparseTable> parseSparseTable(const Record *Rec);
- void emitSparseTable(const SparseTable &Table, raw_ostream &OS);
};
} // End anonymous namespace.
@@ -595,25 +577,56 @@ void SearchableTableEmitter::emitGenericTable(const GenericTable &Table,
emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_IMPL").str(), OS);
+ 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(Table.Entries)) {
+ for (const auto &[Idx, Entry] : enumerate(Entries)) {
OS << " { ";
-
- ListSeparator LS;
- for (const auto &Field : Table.Fields)
- OS << LS
- << primaryRepresentation(Table.Locs[0], Field,
- Entry->getValueInit(Field.Name));
-
+ if (Entry) {
+ ListSeparator LS;
+ for (const auto &Field : Table.Fields)
+ OS << LS
+ << primaryRepresentation(Table.Locs[0], Field,
+ Entry->getValueInit(Field.Name));
+ }
OS << " }, // " << Idx << "\n";
}
OS << " };\n";
- // Indexes are sorted "{ Thing, PrimaryIdx }" arrays, so that a binary
- // search can be performed by "Thing".
- if (Table.PrimaryKey)
+ if (Table.AllowSparseTable) {
+ OS << "\n";
+ emitLookupDeclaration(Table, *Table.PrimaryKey, OS);
+ OS << " {\n";
+ const GenericField &Field = Table.PrimaryKey->Fields[0];
+ OS << " assert(" << Field.Name << " < " << DirectLookupSlots << ");\n";
+ OS << " return &" << Table.Name << "[" << Field.Name << "];\n";
+ OS << "}\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, OS);
+ }
for (const auto &Index : Table.Indices)
emitLookupFunction(Table, *Index, /*IsPrimary=*/false, OS);
@@ -756,128 +769,37 @@ void SearchableTableEmitter::collectTableEntries(
});
}
-std::unique_ptr<SparseTable>
-SearchableTableEmitter::parseSparseTable(const Record *Rec) {
- auto Table = std::make_unique<SparseTable>();
- Table->Name = Rec->getName().str();
- Table->Locs = Rec->getLoc();
- Table->PreprocessorGuard = Rec->getName().str();
- Table->CppTypeName = Rec->getValueAsString("CppTypeName").str();
- int64_t KeyBitsRaw = Rec->getValueAsInt("KeyBits");
- if (KeyBitsRaw <= 0)
- PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
- "' has non-positive KeyBits");
- Table->KeyBits = static_cast<unsigned>(KeyBitsRaw);
- Table->LookupFuncName = Rec->getValueAsString("LookupFuncName").str();
-
- for (StringRef RF : Rec->getValueAsListOfStrings("ResultFields"))
- Table->ResultFields.push_back(RF.str());
-
- if (Table->ResultFields.empty())
- PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
- "' has empty 'ResultFields'");
-
- for (const Record *SrcRec : Rec->getValueAsListOfDefs("Sources")) {
- SparseTableSource Src;
- Src.FilterClass = SrcRec->getValueAsString("FilterClass").str();
- Src.KeyField = SrcRec->getValueAsString("KeyField").str();
-
- if (!Records.getClass(Src.FilterClass))
- PrintFatalError(SrcRec->getValue("FilterClass"),
- Twine("SparseTableSource FilterClass '") +
- Src.FilterClass + "' does not exist");
-
- for (StringRef SF : SrcRec->getValueAsListOfStrings("Fields"))
- Src.Fields.push_back(SF.str());
-
- if (Src.Fields.empty())
- PrintFatalError(SrcRec, Twine("SparseTableSource for table '") +
- Table->Name + "' has empty 'Fields'");
- if (Src.Fields.size() != Table->ResultFields.size())
- PrintFatalError(SrcRec, Twine("SparseTableSource for table '") +
- Table->Name +
- "', 'Fields' length must match "
- "'ResultFields' length");
-
- Table->Sources.push_back(std::move(Src));
- }
-
- if (Table->Sources.empty())
- PrintFatalError(Rec, Twine("SparseTable '") + Table->Name +
- "' has empty 'Sources'");
-
- return Table;
-}
-
-void SearchableTableEmitter::emitSparseTable(const SparseTable &Table,
- raw_ostream &OS) {
- unsigned NumRows = 1u << Table.KeyBits;
- unsigned NumFields = Table.ResultFields.size();
- SmallVector<SmallVector<std::string, 2>> Slots(
- NumRows, SmallVector<std::string, 2>(NumFields));
-
- for (const SparseTableSource &Src : Table.Sources) {
- for (const Record *Entry :
- Records.getAllDerivedDefinitions(Src.FilterClass)) {
- const Init *KeyInit = Entry->getValueInit(Src.KeyField);
- uint64_t Key = static_cast<uint64_t>(getAsInt(KeyInit));
- if (Key >= NumRows)
- PrintFatalError(Entry, Twine("In SparseTable '") + Table.Name +
- "', key value " + Twine(Key) +
- " exceeds 2^KeyBits (" + Twine(NumRows) +
- ")");
- for (unsigned I = 0; I < NumFields; ++I) {
- if (Src.Fields[I].empty())
- continue;
- const Init *FieldInit = Entry->getValueInit(Src.Fields[I]);
- GenericField F(Src.Fields[I]);
- F.RecType = cast<TypedInit>(FieldInit)->getType();
- std::string Val = primaryRepresentation(Table.Locs[0], F, FieldInit);
- if (!Slots[Key][I].empty() && Slots[Key][I] != Val)
- PrintFatalError(Entry, Twine("In SparseTable '") + Table.Name +
- "', key value " + Twine(Key) +
- " has conflicting values for field '" +
- Src.Fields[I] + "'");
- Slots[Key][I] = std::move(Val);
- }
- }
- }
+static bool canUseSparseTable(const Record *TableRec,
+ const std::unique_ptr<GenericTable> &Table) {
+ if (TableRec->getValueAsBit("DisallowSparseTable"))
+ return false;
- emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_DECL").str(), OS);
+ // Sparse tables are only supported with a single primary key.
+ if (Table->PrimaryKey->Fields.size() != 1)
+ return false;
- OS << "const " << Table.CppTypeName << " *" << Table.LookupFuncName
- << "(unsigned Key);\n";
- OS << "#endif\n\n";
+ const auto *KeyBits =
+ dyn_cast<BitsRecTy>(Table->PrimaryKey->Fields[0].RecType);
- emitIfdef((Twine("GET_") + Table.PreprocessorGuard + "_IMPL").str(), OS);
+ // Sparse tables only support `bits` key.
+ if (!KeyBits)
+ return false;
- OS << "constexpr " << Table.CppTypeName << " " << Table.Name << "[] = {\n";
- for (unsigned I = 0; I < NumRows; ++I) {
- OS << " { ";
- if (llvm::any_of(Slots[I],
- [](const std::string &V) { return !V.empty(); })) {
- ListSeparator LS;
- for (const std::string &Val : Slots[I])
- OS << LS << (Val.empty() ? "{}" : Val);
- }
- OS << " }, // " << I << "\n";
- }
- OS << " };\n";
+ // Sparse tables are not compatible with PrimaryKeyReturnRange.
+ if (TableRec->getValueAsBit("PrimaryKeyReturnRange"))
+ return false;
- OS << "const " << Table.CppTypeName << " *" << Table.LookupFuncName
- << "(unsigned Key) {\n";
- OS << " assert(Key < " << NumRows << " && \"" << Table.LookupFuncName
- << ": key out of range\");\n";
- OS << " return &" << Table.Name << "[Key];\n";
- OS << "}\n";
+ // Only support tables up to 4k in size.
+ constexpr unsigned MaxKeyBits = 12;
+ if (KeyBits->getNumBits() > MaxKeyBits)
+ return false;
- OS << "#endif\n\n";
+ 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;
- SmallVector<std::unique_ptr<SparseTable>, 4> SparseTables;
DenseMap<const Record *, GenericTable *> TableMap;
bool NeedsTarget =
!Records.getAllDerivedDefinitionsIfDefined("Instruction").empty() ||
@@ -978,6 +900,8 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
[&](const Record *LHS, const Record *RHS) {
return compareBy(LHS, RHS, *Table->PrimaryKey);
});
+
+ Table->AllowSparseTable = canUseSparseTable(TableRec, Table);
}
TableMap.try_emplace(TableRec, Table.get());
@@ -999,11 +923,10 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
Table, IndexRec->getValue("Key"), IndexRec->getName(),
IndexRec->getValueAsListOfStrings("Key"),
IndexRec->getValueAsBit("EarlyOut"), /*ReturnRange*/ false));
- }
- for (const Record *TableRec :
- Records.getAllDerivedDefinitionsIfDefined("SparseTable"))
- SparseTables.push_back(parseSparseTable(TableRec));
+ // Sparse tables with secondary search indices are not supported.
+ Table.AllowSparseTable = false;
+ }
// Translate legacy tables.
const Record *SearchableTable = Records.getClass("SearchableTable");
@@ -1072,9 +995,6 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
for (const auto &Table : Tables)
emitGenericTable(*Table, OS);
- for (const auto &Table : SparseTables)
- emitSparseTable(*Table, OS);
-
// Put all #undefs last, to allow multiple sections guarded by the same
// define.
for (const auto &Guard : PreprocessorGuards)
>From fbb6d9e4aa2bc38a57360b75112398ec62db9040 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at amd.com>
Date: Mon, 8 Jun 2026 15:07:41 +0100
Subject: [PATCH 3/4] Return nullptr
---
.../utils/TableGen/SearchableTableEmitter.cpp | 20 ++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/llvm/utils/TableGen/SearchableTableEmitter.cpp b/llvm/utils/TableGen/SearchableTableEmitter.cpp
index adbca498307e3..61e7a72fc79ca 100644
--- a/llvm/utils/TableGen/SearchableTableEmitter.cpp
+++ b/llvm/utils/TableGen/SearchableTableEmitter.cpp
@@ -603,12 +603,22 @@ void SearchableTableEmitter::emitGenericTable(const GenericTable &Table,
OS << "constexpr " << Table.CppTypeName << " " << Table.Name << "[] = {\n";
for (const auto &[Idx, Entry] : enumerate(Entries)) {
OS << " { ";
+ ListSeparator LS;
if (Entry) {
- ListSeparator LS;
for (const auto &Field : Table.Fields)
OS << LS
<< primaryRepresentation(Table.Locs[0], Field,
Entry->getValueInit(Field.Name));
+ } 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" << utohexstr((DirectLookupSlots - 1) ^ Idx);
+ else
+ OS << "{}";
+ }
}
OS << " }, // " << Idx << "\n";
}
@@ -619,8 +629,12 @@ void SearchableTableEmitter::emitGenericTable(const GenericTable &Table,
emitLookupDeclaration(Table, *Table.PrimaryKey, OS);
OS << " {\n";
const GenericField &Field = Table.PrimaryKey->Fields[0];
- OS << " assert(" << Field.Name << " < " << DirectLookupSlots << ");\n";
- OS << " return &" << Table.Name << "[" << Field.Name << "];\n";
+ OS << " if (" << Field.Name << " >= " << DirectLookupSlots << ")\n";
+ OS << " return nullptr;\n";
+ OS << " const auto *Entry = &" << Table.Name << "[" << Field.Name
+ << "];\n";
+ OS << " return Entry->" << Field.Name << " == " << Field.Name
+ << " ? Entry : nullptr;\n";
OS << "}\n";
} else if (Table.PrimaryKey) {
// Indexes are sorted "{ Thing, PrimaryIdx }" arrays, so that a binary
>From 7cead95c174efb8315f33677dd3c2224b23b60c2 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at amd.com>
Date: Mon, 8 Jun 2026 17:26:47 +0100
Subject: [PATCH 4/4] Update docs
---
llvm/docs/TableGen/BackEnds.rst | 84 +++++++++++++++++++++++++++++++--
1 file changed, 80 insertions(+), 4 deletions(-)
diff --git a/llvm/docs/TableGen/BackEnds.rst b/llvm/docs/TableGen/BackEnds.rst
index ea74dfd217b2e..4900e4457484f 100644
--- a/llvm/docs/TableGen/BackEnds.rst
+++ b/llvm/docs/TableGen/BackEnds.rst
@@ -702,7 +702,7 @@ TableGen produces C++ code to define the table entries and also produces
the declaration and definition of a function to search the table based on a
primary key. To define the table, define a record whose parent class is
``GenericTable`` and whose name is the name of the global table of entries.
-This class provides six fields.
+This class provides the following fields.
* ``string FilterClass``. The table will have one entry for each record
that derives from this class.
@@ -735,6 +735,11 @@ This class provides six fields.
specified by the lookup function. Currently, it is supported only for primary
lookup functions. Refer to the second example below for further details.
+* ``bit DisallowSparseTable``. When set to 1 (the default), prevents the
+ emitter from generating a sparse direct-lookup array for this table. Set to
+ 0 to allow the emitter to emit a sparse directly-indexed array when the
+ primary key qualifies (see below).
+
TableGen attempts to deduce the type of each of the table fields so that it
can format the C++ initializers in the emitted table. It can deduce ``bit``,
``bits<n>``, ``string``, ``Intrinsic``, and ``Instruction``. These can be
@@ -835,9 +840,24 @@ The table entries in ``ATable`` are sorted in order by ``Val1``, and within
each of those values, by ``Val2``. This allows a binary search of the table,
which is performed in the lookup function by ``std::lower_bound``. The
lookup function returns a reference to the found table entry, or the null
-pointer if no entry is found. If the table has a single primary key field
-which is integral and densely numbered, a direct lookup is generated rather
-than a binary search.
+pointer if no entry is found.
+
+The emitter selects the lookup strategy automatically based on the primary key:
+
+* **Dense direct lookup**: used when the primary key is a single integral field
+ whose values form a contiguous range. A compact array is emitted and the
+ lookup function indexes into it directly.
+
+* **Sparse direct lookup**: used when ``DisallowSparseTable = 0``, the primary
+ key is a single ``bits<N>`` field with N ≤ 12, ``PrimaryKeyReturnRange`` is
+ false, and the table has no secondary search indexes. The emitter allocates a
+ directly-indexed array of ``2^N`` entries. Empty slots are filled with a
+ sentinel value in the key field such that the in-place key comparison in the
+ lookup function returns ``nullptr`` for them. This trades memory for
+ O(1) lookup.
+
+* **Binary search**: the default for all other cases. Entries are sorted by the
+ primary key and ``std::lower_bound`` is used in the lookup function.
This example includes a field whose type TableGen cannot deduce. The ``Kind``
field uses the enumerated type ``CEnum`` defined above. To inform TableGen
@@ -1068,6 +1088,62 @@ The generated tables are:
{ 0x9 }, // 4
};
+Here is an example of a sparse direct-lookup table. ``FTable`` uses a 4-bit
+primary key (``bits<4>``), so the emitter allocates an array of
+``2^4 = 16`` entries.
+
+.. code-block:: text
+
+ def FTable : GenericTable {
+ let FilterClass = "FEntry";
+ let Fields = ["Key", "Val"];
+ let PrimaryKey = ["Key"];
+ let PrimaryKeyName = "lookupFTableByKey";
+ let DisallowSparseTable = false;
+ }
+
+Here is the generated C++ code. The declaration of ``lookupFTableByKey`` is
+guarded by ``GET_FTable_DECL``, while the definitions are guarded by
+``GET_FTable_IMPL``.
+
+.. code-block:: text
+
+ #ifdef GET_FTable_DECL
+ const FEntry *lookupFTableByKey(uint8_t Key);
+ #endif
+
+ #ifdef GET_FTable_IMPL
+ constexpr FEntry FTable[] = {
+ { 0xF, 0x0 }, // 0
+ { 0xE, 0x0 }, // 1
+ { 0x2, 0xA }, // 2
+ { 0xC, 0x0 }, // 3
+ { 0xB, 0x0 }, // 4
+ { 0x5, 0x14 }, // 5
+ { 0x9, 0x0 }, // 6
+ { 0x8, 0x0 }, // 7
+ { 0x7, 0x0 }, // 8
+ { 0x6, 0x0 }, // 9
+ { 0x5, 0x0 }, // 10
+ { 0x4, 0x0 }, // 11
+ { 0x3, 0x0 }, // 12
+ { 0xD, 0x1E }, // 13
+ { 0x1, 0x0 }, // 14
+ { 0x0, 0x0 }, // 15
+ };
+
+ const FEntry *lookupFTableByKey(uint8_t Key) {
+ if (Key >= 16)
+ return nullptr;
+ const auto *Entry = &FTable[Key];
+ return Entry->Key == Key ? Entry : nullptr;
+ }
+ #endif
+
+Empty slots are filled with a sentinel value in the ``Key`` field
+(``(15 ^ Idx)``) so that the in-place comparison ``Entry->Key == Key``
+returns ``nullptr`` for them without a separate presence array.
+
Search Indexes
~~~~~~~~~~~~~~
More information about the llvm-commits
mailing list