[llvm] [TableGen] Add GenericEnumClass that generates scoped enums (PR #170903)
Mirko BrkuĊĦanin via llvm-commits
llvm-commits at lists.llvm.org
Mon Dec 8 09:48:29 PST 2025
https://github.com/mbrkusanin updated https://github.com/llvm/llvm-project/pull/170903
>From 1cc31978d3c742cca41f9965f3dedb6d8818f8ef Mon Sep 17 00:00:00 2001
From: Mirko Brkusanin <Mirko.Brkusanin at amd.com>
Date: Fri, 5 Dec 2025 19:18:50 +0100
Subject: [PATCH 1/2] [TableGen] Add GenericEnumClass that generates scoped
enums
Add a way of generating scoped enums with c++ `enum class` when a
new name scope is preferable.
---
llvm/docs/TableGen/BackEnds.rst | 30 ++++++
llvm/include/llvm/TableGen/SearchableTable.td | 7 ++
llvm/test/TableGen/generic-tables.td | 98 +++++++++++++++++++
.../utils/TableGen/SearchableTableEmitter.cpp | 24 ++++-
4 files changed, 156 insertions(+), 3 deletions(-)
diff --git a/llvm/docs/TableGen/BackEnds.rst b/llvm/docs/TableGen/BackEnds.rst
index 1e3cb8783df16..166292773b1e6 100644
--- a/llvm/docs/TableGen/BackEnds.rst
+++ b/llvm/docs/TableGen/BackEnds.rst
@@ -629,6 +629,12 @@ using the ``let`` statement.
field, it will be assigned an integer value. Values are assigned in
alphabetical order starting with 0.
+* ``bit Scoped``. When set to 1, the generated C++ declaration will use
+ ``enum class`` instead of an unscoped ``enum``. This defaults to 0.
+
+To make it easier to request scoped enums, the ``GenericEnumClass`` helper
+derives from ``GenericEnum`` and sets ``Scoped = 1``.
+
Here is an example where the values of the elements are specified
explicitly, as a template argument to the ``BEntry`` class. The resulting
C++ code is shown.
@@ -688,6 +694,30 @@ by element name.
};
#endif
+Here is an example of a ``GenericEnumClass`` that generates scoped enumerated
+values by emitting an ``enum class`` declaration.
+
+.. code-block:: text
+
+ class FEntry;
+
+ def FApple : FEntry;
+ def FBanana : FEntry;
+ def FCherry : FEntry;
+
+ def FEnum : GenericEnumClass {
+ let FilterClass = "FEntry";
+ }
+
+.. code-block:: text
+
+ #ifdef GET_FEnum_DECL
+ enum class FEnum {
+ FApple = 0,
+ FBanana = 1,
+ FCherry = 2,
+ }
+ #endif
Generic Tables
~~~~~~~~~~~~~~
diff --git a/llvm/include/llvm/TableGen/SearchableTable.td b/llvm/include/llvm/TableGen/SearchableTable.td
index f10e1597d8da7..6834134a20f2d 100644
--- a/llvm/include/llvm/TableGen/SearchableTable.td
+++ b/llvm/include/llvm/TableGen/SearchableTable.td
@@ -47,6 +47,13 @@ class GenericEnum {
// If ValueField is not set, enum values will be assigned automatically,
// starting at 0, according to a lexicographical sort of the entry names.
string ValueField;
+
+ // If true, emit the enum as a scoped C++ enum class.
+ bit Scoped = 0;
+}
+
+class GenericEnumClass : GenericEnum {
+ let Scoped = 1;
}
// Define a record derived from this class to generate a generic table. This
diff --git a/llvm/test/TableGen/generic-tables.td b/llvm/test/TableGen/generic-tables.td
index 8638740a8e565..c8a7ff75e61c2 100644
--- a/llvm/test/TableGen/generic-tables.td
+++ b/llvm/test/TableGen/generic-tables.td
@@ -19,6 +19,13 @@ include "llvm/TableGen/SearchableTable.td"
// CHECK: CFoo
// CHECK: }
+// CHECK-LABEL: GET_FEnum_DECL
+// CHECK: enum class FEnum {
+// CHECK: FApple = 0,
+// CHECK: FBanana = 1,
+// CHECK: FCherry = 2,
+// CHECK: }
+
// CHECK-LABEL: GET_ATable_DECL
// CHECK: const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2);
@@ -237,3 +244,94 @@ def EEntryOddTable : GenericTable {
let PrimaryKey = ["Value"];
let PrimaryKeyName = "lookupEEntryOddTableByValue";
}
+
+// CHECK-LABEL: GET_FTable_DECL
+// CHECK: const FTableEntry *lookupFTableByEnum(FEnum Enum);
+// CHECK: const FTableEntry *lookupFTableByEnumAndValue(FEnum Enum, uint32_t Value);
+
+// CHECK-LABEL: GET_FTable_IMPL
+// CHECK: constexpr FTableEntry FTable[] = {
+// CHECK: { FEnum::FApple, 0x1 },
+// CHECK: { FEnum::FBanana, 0x2 },
+// CHECK: { FEnum::FCherry, 0x3 },
+// CHECK: };
+// CHECK: const FTableEntry *lookupFTableByEnum(FEnum Enum) {
+// CHECK: if ((FEnum)Enum != std::clamp((FEnum)Enum, (FEnum)FEnum::FApple, (FEnum)FEnum::FCherry))
+// CHECK: return nullptr;
+// CHECK: const FTableEntry *lookupFTableByEnumAndValue(FEnum Enum, uint32_t Value) {
+// CHECK: struct IndexType {
+// CHECK: FEnum Enum;
+// CHECK: uint32_t Value;
+// CHECK: unsigned _index;
+// CHECK: };
+// CHECK: static const struct IndexType Index[] = {
+// CHECK: { FEnum::FApple, 0x1, 0 },
+// CHECK: { FEnum::FBanana, 0x2, 1 },
+// CHECK: { FEnum::FCherry, 0x3, 2 },
+// CHECK: };
+// CHECK: struct KeyType {
+// CHECK: FEnum Enum;
+// CHECK: uint32_t Value;
+// CHECK: };
+// CHECK: return &FTable[Idx->_index];
+
+// CHECK-LABEL: GET_FTableContiguous_DECL
+// CHECK: const FTableEntry *lookupFTableContiguousByEnum(FEnum Enum);
+
+// CHECK-LABEL: GET_FTableContiguous_IMPL
+// CHECK: constexpr FTableEntry FTableContiguous[] = {
+// CHECK: { FEnum::FApple, 0x1 },
+// CHECK: { FEnum::FBanana, 0x2 },
+// CHECK: { FEnum::FCherry, 0x3 },
+// CHECK: };
+// CHECK: const FTableEntry *lookupFTableContiguousByEnum(FEnum Enum) {
+// CHECK: if ((FEnum)Enum != std::clamp((FEnum)Enum, (FEnum)FEnum::FApple, (FEnum)FEnum::FCherry))
+// CHECK: return nullptr;
+// CHECK: auto Table = ArrayRef(FTableContiguous);
+// CHECK: size_t Idx = (unsigned)Enum - (unsigned)FEnum::FApple;
+// CHECK: return &Table[Idx];
+
+class FEntry;
+
+def FApple : FEntry;
+def FBanana : FEntry;
+def FCherry : FEntry;
+
+def FEnum : GenericEnumClass {
+ let FilterClass = "FEntry";
+}
+
+class FTableEntry<FEntry EnumValue, int val> {
+ FEntry Enum = EnumValue;
+ bits<32> Value = val;
+}
+
+def FAppleEntry : FTableEntry<FApple, 1>;
+def FBananaEntry : FTableEntry<FBanana, 2>;
+def FCherryEntry : FTableEntry<FCherry, 3>;
+
+def FTable : GenericTable {
+ let FilterClass = "FTableEntry";
+ let Fields = ["Enum", "Value"];
+
+ string TypeOf_Enum = "FEnum";
+
+ let PrimaryKey = ["Enum"];
+ let PrimaryKeyName = "lookupFTableByEnum";
+ let PrimaryKeyEarlyOut = 1;
+}
+
+def lookupFTableByEnumAndValue : SearchIndex {
+ let Table = FTable;
+ let Key = ["Enum", "Value"];
+}
+
+def FTableContiguous : GenericTable {
+ let FilterClass = "FTableEntry";
+ let Fields = ["Enum", "Value"];
+
+ string TypeOf_Enum = "FEnum";
+
+ let PrimaryKey = ["Enum"];
+ let PrimaryKeyName = "lookupFTableContiguousByEnum";
+}
diff --git a/llvm/utils/TableGen/SearchableTableEmitter.cpp b/llvm/utils/TableGen/SearchableTableEmitter.cpp
index 0dc8c92a5a37a..7514ca33c706f 100644
--- a/llvm/utils/TableGen/SearchableTableEmitter.cpp
+++ b/llvm/utils/TableGen/SearchableTableEmitter.cpp
@@ -55,6 +55,7 @@ struct GenericEnum {
const Record *Class = nullptr;
std::string PreprocessorGuard;
MapVector<const Record *, Entry> Entries;
+ bool Scoped = false;
const Entry *getEntry(const Record *Def) const {
auto II = Entries.find(Def);
@@ -146,6 +147,12 @@ class SearchableTableEmitter {
if (!Entry)
PrintFatalError(Loc,
Twine("Entry for field '") + Field.Name + "' is null");
+ if (Field.Enum->Scoped) {
+ std::string QualifiedName = Field.Enum->Name;
+ QualifiedName += "::";
+ QualifiedName += Entry->Name;
+ return QualifiedName;
+ }
return Entry->Name.str();
}
PrintFatalError(Loc, Twine("invalid field type for field '") + Field.Name +
@@ -193,7 +200,12 @@ class SearchableTableEmitter {
}
if (isa<BitRecTy>(Field.RecType))
return "bool";
- if (Field.Enum || Field.IsIntrinsic || Field.IsInstruction)
+ if (Field.Enum) {
+ if (Field.Enum->Scoped)
+ return Field.Enum->Name;
+ return "unsigned";
+ }
+ if (Field.IsIntrinsic || Field.IsInstruction)
return "unsigned";
PrintFatalError(Index.Loc,
Twine("In table '") + Table.Name + "' lookup method '" +
@@ -336,7 +348,7 @@ void SearchableTableEmitter::emitGenericEnum(const GenericEnum &Enum,
raw_ostream &OS) {
emitIfdef((Twine("GET_") + Enum.PreprocessorGuard + "_DECL").str(), OS);
- OS << "enum " << Enum.Name << " {\n";
+ OS << "enum " << (Enum.Scoped ? "class " : "") << Enum.Name << " {\n";
for (const auto &[Name, Value] :
make_second_range(Enum.Entries.getArrayRef()))
OS << " " << Name << " = " << Value << ",\n";
@@ -438,7 +450,12 @@ void SearchableTableEmitter::emitLookupFunction(const GenericTable &Table,
if (IsContiguous && !Index.EarlyOut) {
OS << " auto Table = ArrayRef(" << IndexName << ");\n";
- OS << " size_t Idx = " << Field.Name << " - " << FirstRepr << ";\n";
+ std::string NumericField =
+ Field.Enum ? "(unsigned)" + Field.Name : Field.Name;
+ std::string NumericFirstRepr =
+ Field.Enum ? "(unsigned)" + FirstRepr : FirstRepr;
+ OS << " size_t Idx = " << NumericField << " - " << NumericFirstRepr
+ << ";\n";
OS << " return ";
if (IsPrimary)
OS << "&Table[Idx]";
@@ -756,6 +773,7 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
auto Enum = std::make_unique<GenericEnum>();
Enum->Name = EnumRec->getName().str();
Enum->PreprocessorGuard = EnumRec->getName().str();
+ Enum->Scoped = EnumRec->getValueAsBit("Scoped");
StringRef FilterClass = EnumRec->getValueAsString("FilterClass");
Enum->Class = Records.getClass(FilterClass);
>From 2ccda41f825c72b3f4ac2d8334f6b4c2b83cd703 Mon Sep 17 00:00:00 2001
From: Mirko Brkusanin <Mirko.Brkusanin at amd.com>
Date: Mon, 8 Dec 2025 17:58:25 +0100
Subject: [PATCH 2/2] Add size field to choose enum class type
---
llvm/docs/TableGen/BackEnds.rst | 7 +-
llvm/include/llvm/TableGen/SearchableTable.td | 5 ++
.../searchabletables-scoped-size-error.td | 17 ++++
.../utils/TableGen/SearchableTableEmitter.cpp | 80 +++++++++++++++----
4 files changed, 92 insertions(+), 17 deletions(-)
create mode 100644 llvm/test/TableGen/searchabletables-scoped-size-error.td
diff --git a/llvm/docs/TableGen/BackEnds.rst b/llvm/docs/TableGen/BackEnds.rst
index 166292773b1e6..6d01fe2029402 100644
--- a/llvm/docs/TableGen/BackEnds.rst
+++ b/llvm/docs/TableGen/BackEnds.rst
@@ -632,6 +632,10 @@ using the ``let`` statement.
* ``bit Scoped``. When set to 1, the generated C++ declaration will use
``enum class`` instead of an unscoped ``enum``. This defaults to 0.
+* ``int Size``. (Optional) When emitting a scoped enum, provides the bit width
+ of the underlying integer type. Supported values are 8, 16, 32, and 64; when
+ unset, the compiler's default underlying type is used.
+
To make it easier to request scoped enums, the ``GenericEnumClass`` helper
derives from ``GenericEnum`` and sets ``Scoped = 1``.
@@ -707,12 +711,13 @@ values by emitting an ``enum class`` declaration.
def FEnum : GenericEnumClass {
let FilterClass = "FEntry";
+ let Size = 8;
}
.. code-block:: text
#ifdef GET_FEnum_DECL
- enum class FEnum {
+ enum class FEnum : uint8_t {
FApple = 0,
FBanana = 1,
FCherry = 2,
diff --git a/llvm/include/llvm/TableGen/SearchableTable.td b/llvm/include/llvm/TableGen/SearchableTable.td
index 6834134a20f2d..98ddc59ebe324 100644
--- a/llvm/include/llvm/TableGen/SearchableTable.td
+++ b/llvm/include/llvm/TableGen/SearchableTable.td
@@ -50,6 +50,11 @@ class GenericEnum {
// If true, emit the enum as a scoped C++ enum class.
bit Scoped = 0;
+
+ // (Optional) Bit width of the underlying integral type when emitting a scoped
+ // enum class. When unset, the compiler's default underlying type is used.
+ // Supported widths are 8, 16, 32, and 64.
+ int Size = ?;
}
class GenericEnumClass : GenericEnum {
diff --git a/llvm/test/TableGen/searchabletables-scoped-size-error.td b/llvm/test/TableGen/searchabletables-scoped-size-error.td
new file mode 100644
index 0000000000000..5e0119ac28ed0
--- /dev/null
+++ b/llvm/test/TableGen/searchabletables-scoped-size-error.td
@@ -0,0 +1,17 @@
+// RUN: not llvm-tblgen -gen-searchable-tables -I %p/../../include %s 2>&1 | FileCheck %s
+
+include "llvm/TableGen/SearchableTable.td"
+
+class SmallEnumEntry;
+
+foreach I = 0-299 in {
+ def Entry#I : SmallEnumEntry;
+}
+
+def SmallScopedEnum : GenericEnumClass {
+ let FilterClass = "SmallEnumEntry";
+ let Size = 8;
+ // CHECK: [[@LINE-1]]:7: error: Scoped enum 'SmallScopedEnum' with size in bits of '8' is too small for the total number of entries '300'
+ // CHECK-NEXT: let Size = 8;
+}
+
diff --git a/llvm/utils/TableGen/SearchableTableEmitter.cpp b/llvm/utils/TableGen/SearchableTableEmitter.cpp
index 7514ca33c706f..3a8fefad0f204 100644
--- a/llvm/utils/TableGen/SearchableTableEmitter.cpp
+++ b/llvm/utils/TableGen/SearchableTableEmitter.cpp
@@ -43,6 +43,20 @@ static int64_t getInt(const Record *R, StringRef Field) {
return getAsInt(R->getValueInit(Field));
}
+static std::string bitWidthToUInt(unsigned NumBits) {
+ if (NumBits == 0)
+ return "";
+ if (NumBits <= 8)
+ return "uint8_t";
+ if (NumBits <= 16)
+ return "uint16_t";
+ if (NumBits <= 32)
+ return "uint32_t";
+ if (NumBits <= 64)
+ return "uint64_t";
+ return "";
+}
+
namespace {
struct GenericEnum {
struct Entry {
@@ -56,6 +70,7 @@ struct GenericEnum {
std::string PreprocessorGuard;
MapVector<const Record *, Entry> Entries;
bool Scoped = false;
+ unsigned ScopedSize = 0;
const Entry *getEntry(const Record *Def) const {
auto II = Entries.find(Def);
@@ -184,15 +199,10 @@ class SearchableTableEmitter {
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";
+ std::string Type = bitWidthToUInt(BI->getNumBits());
+ if (!Type.empty())
+ return Type;
+
PrintFatalError(Index.Loc, Twine("In table '") + Table.Name +
"' lookup method '" + Index.Name +
"', key field '" + Field.Name +
@@ -226,7 +236,8 @@ class SearchableTableEmitter {
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);
+ StringRef ValueField, ArrayRef<const Record *> Items,
+ const Record *EnumRec = nullptr);
void collectTableEntries(GenericTable &Table, ArrayRef<const Record *> Items);
int64_t getNumericKey(const SearchIndex &Index, const Record *Rec);
};
@@ -348,7 +359,13 @@ void SearchableTableEmitter::emitGenericEnum(const GenericEnum &Enum,
raw_ostream &OS) {
emitIfdef((Twine("GET_") + Enum.PreprocessorGuard + "_DECL").str(), OS);
- OS << "enum " << (Enum.Scoped ? "class " : "") << Enum.Name << " {\n";
+ OS << "enum " << (Enum.Scoped ? "class " : "") << Enum.Name;
+ if (Enum.Scoped && Enum.ScopedSize != 0) {
+ std::string Type = bitWidthToUInt(Enum.ScopedSize);
+ assert(!Type.empty() && "Invalid enum underlying size");
+ OS << " : " << Type;
+ }
+ OS << " {\n";
for (const auto &[Name, Value] :
make_second_range(Enum.Entries.getArrayRef()))
OS << " " << Name << " = " << Value << ",\n";
@@ -668,9 +685,11 @@ std::unique_ptr<SearchIndex> SearchableTableEmitter::parseSearchIndex(
return Index;
}
-void SearchableTableEmitter::collectEnumEntries(
- GenericEnum &Enum, StringRef NameField, StringRef ValueField,
- ArrayRef<const Record *> Items) {
+void SearchableTableEmitter::collectEnumEntries(GenericEnum &Enum,
+ StringRef NameField,
+ StringRef ValueField,
+ ArrayRef<const Record *> Items,
+ const Record *EnumRec) {
Enum.Entries.reserve(Items.size());
for (const Record *EntryRec : Items) {
StringRef Name = NameField.empty() ? EntryRec->getName()
@@ -694,6 +713,22 @@ void SearchableTableEmitter::collectEnumEntries(
for (auto [Idx, Entry] : enumerate(SavedEntries))
Enum.Entries.try_emplace(Entry.first, Entry.second.Name, Idx);
}
+
+ // If this is a scoped enum with custom type size, check if all entries can
+ // fit.
+ if (Enum.Scoped && Enum.ScopedSize != 0 &&
+ (1 << Enum.ScopedSize) < Enum.Entries.size()) {
+ auto Msg = Twine("Scoped enum '") + Enum.Name + "' with size in bits of '" +
+ Twine(Enum.ScopedSize) +
+ "' is too small for the total number of entries '" +
+ Twine(Enum.Entries.size()) + "'";
+ if (EnumRec && !EnumRec->isValueUnset("Size"))
+ if (const RecordVal *SizeVal = EnumRec->getValue("Size"))
+ PrintFatalError(SizeVal, Msg);
+ if (EnumRec)
+ PrintFatalError(EnumRec, Msg);
+ PrintFatalError(Msg);
+ }
}
void SearchableTableEmitter::collectTableEntries(
@@ -782,8 +817,21 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
Twine("Enum FilterClass '") + FilterClass +
"' does not exist");
+ if (!EnumRec->isValueUnset("Size")) {
+ int Size = EnumRec->getValueAsInt("Size");
+ if (Size <= 0)
+ PrintFatalError(EnumRec->getValue("Size"),
+ Twine("Enum Size must be positive, got ") +
+ Twine(Size));
+ std::string Type = bitWidthToUInt(static_cast<unsigned>(Size));
+ if (Type.empty())
+ PrintFatalError(EnumRec->getValue("Size"),
+ "Enum Size must be between 1 and 64");
+ Enum->ScopedSize = static_cast<unsigned>(Size);
+ }
+
collectEnumEntries(*Enum, NameField, ValueField,
- Records.getAllDerivedDefinitions(FilterClass));
+ Records.getAllDerivedDefinitions(FilterClass), EnumRec);
EnumMap.try_emplace(EnumRec, Enum.get());
Enums.emplace_back(std::move(Enum));
}
@@ -809,7 +857,7 @@ void SearchableTableEmitter::run(raw_ostream &OS) {
FieldName +
"': " + TypeOfRecordVal->getValue()->getAsString());
PrintFatalNote("The 'TypeOf_xxx' field must be a string naming a "
- "GenericEnum record, or \"code\"");
+ "GenericEnum(Class) record, or \"code\"");
}
}
}
More information about the llvm-commits
mailing list