[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