[llvm] [Support] Add compact enum table (PR #202044)

Alexis Engelke via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 26 04:39:50 PDT 2026


https://github.com/aengelke updated https://github.com/llvm/llvm-project/pull/202044

>From f272347c9451a6bdb5d18a09fb5a1725772e1943 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Sat, 6 Jun 2026 13:55:47 +0000
Subject: [PATCH 1/2] [spr] initial version

Created using spr 1.3.8-wip
---
 llvm/include/llvm/Support/Enum.h             | 153 +++++++++
 llvm/include/llvm/Support/ScopedPrinter.h    |  45 +++
 llvm/unittests/Support/ScopedPrinterTest.cpp | 320 +++++++++++++++++++
 3 files changed, 518 insertions(+)
 create mode 100644 llvm/include/llvm/Support/Enum.h

diff --git a/llvm/include/llvm/Support/Enum.h b/llvm/include/llvm/Support/Enum.h
new file mode 100644
index 0000000000000..ccab45769bb5e
--- /dev/null
+++ b/llvm/include/llvm/Support/Enum.h
@@ -0,0 +1,153 @@
+//===-----------------------------------------------------------*- C++ -*--===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Compact table to map enumeration to strings, generated at compile-time. Basic
+// usage:
+//
+//     // Don't reference this elsewhere, this should _not_ go into the final
+//     // binary!
+//     constexpr EnumStringDef<EnumTy> Defs[] = {
+//         {{"abc"}, 1},
+//         {{"def"}, 2},
+//     };
+//     static constexpr auto EnumStrs = BUILD_ENUM_STRINGS(Defs);
+//     // ...
+//     StringRef Str = EnumStrings(EnumStrs).toString(2); // "def"
+//
+// The table supports multiple strings per enumeration entry for alternative
+// representations, this is e.g. used by llvm-readobj for LLVM and GNU names.
+//
+// Internally, "EnumStrs" above holds the array of enum definitions (EnumString)
+// and the actual strings in one data structure. This permits the EnumString to
+// reference the string with a small 16-bit offset from its location in memory.
+// This design allows for a small size and doesn't require any relocations.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_ENUM_H
+#define LLVM_SUPPORT_ENUM_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/DataTypes.h"
+
+namespace llvm {
+
+/// Compile-time data representation of enum entries. Only use for constexpr
+/// variables passed to BUILD_ENUM_STRINGS, do NOT access the created variable
+/// directly in code! The idea is that this is only used at compile-time to
+/// build a more compact and relocation-free representation in the binary.
+template <typename T, unsigned NumStrs = 1> struct EnumStringDef {
+  // Actual length name is irrelevant, this is not stored in the binary. Bump
+  // if a larger maximum string name is needed.
+  std::array<char[72], NumStrs> Names;
+  T Value;
+};
+
+template <typename T, unsigned NumStrs = 1> class EnumString {
+  template <typename, unsigned, size_t, size_t> friend class EnumStringsStorage;
+
+  uint16_t NameOff[NumStrs] = {}; ///< Name offsets relative to this pointer.
+  uint8_t NameSize[NumStrs] = {}; ///< Name string lengths.
+  T Value{};                      ///< Enumeration value.
+
+  constexpr EnumString() {}
+  // Because name strings are stored relative to the address of this EnumString
+  // in (read-only) memory, EnumString is neither movable nor copyable.
+  EnumString(const EnumString &) = delete;
+  EnumString &operator=(const EnumString &) = delete;
+
+public:
+  constexpr StringRef name(unsigned Idx = 0) const {
+    assert(Idx < NumStrs);
+    return {reinterpret_cast<const char *>(this) + NameOff[Idx], NameSize[Idx]};
+  }
+  constexpr T value() const { return Value; }
+};
+
+namespace detail {
+template <typename T, unsigned NumStrs, size_t N>
+constexpr unsigned
+enumStringsStorageSize(const EnumStringDef<T, NumStrs> (&Entries)[N]) {
+  unsigned Len = 0;
+  for (unsigned i = 0; i != N; i++)
+    for (unsigned j = 0; j != NumStrs; j++)
+      Len += std::string_view(Entries[i].Names[j]).size();
+  return Len;
+}
+} // namespace detail
+
+template <typename T, unsigned NumStrs, size_t N, size_t StrLen>
+struct EnumStringsStorage {
+  EnumString<T, NumStrs> Data[N];
+  char Strs[StrLen];
+
+  constexpr EnumStringsStorage(const EnumStringDef<T, NumStrs> (&Entries)[N])
+      : Data{}, Strs{} {
+    unsigned StrIdx = 0;
+    for (unsigned i = 0; i < N; i++) {
+      Data[i].Value = Entries[i].Value;
+      for (unsigned j = 0; j < NumStrs; j++) {
+        unsigned StrOff = offsetof(EnumStringsStorage, Strs[StrIdx]) -
+                          offsetof(EnumStringsStorage, Data[i]);
+        std::string_view Name(Entries[i].Names[j]);
+        Data[i].NameSize[j] = Name.size();
+        Data[i].NameOff[j] = uint16_t(StrOff);
+        for (char C : Name)
+          Strs[StrIdx++] = C;
+      }
+    };
+  }
+};
+
+#define BUILD_ENUM_STRINGS(Tab)                                                \
+  (::llvm::EnumStringsStorage<decltype(Tab[0].Value), Tab[0].Names.size(),     \
+                              sizeof(Tab) / sizeof(Tab[0]),                    \
+                              ::llvm::detail::enumStringsStorageSize(Tab)>{    \
+      Tab})
+
+template <typename T, unsigned NumStrs = 1> class EnumStrings {
+public:
+  using EnumString = EnumString<T, NumStrs>;
+
+  template <size_t N, size_t StrLen>
+  EnumStrings(const EnumStringsStorage<T, NumStrs, N, StrLen> &Table)
+      : EnumValues(Table.Data, N) {}
+
+  template <typename TValue>
+  StringRef toString(TValue Value, unsigned StrIdx = 0) const {
+    // TODO: optimize with binary search?
+    for (const auto &EnumItem : EnumValues)
+      if (EnumItem.value() == Value)
+        return EnumItem.name(StrIdx);
+    return "";
+  }
+
+  template <typename TValue>
+  std::string toStringOrHex(TValue Value, unsigned StrIdx = 0) const {
+    if (StringRef Str = toString(Value, StrIdx); !Str.empty())
+      return Str.str();
+    return utohexstr(Value, true);
+  }
+
+  const EnumString *begin() const { return EnumValues.begin(); }
+  const EnumString *end() const { return EnumValues.end(); }
+
+private:
+  ArrayRef<EnumString> EnumValues;
+};
+
+template <typename T, unsigned NumStrs, size_t N, size_t StrLen>
+EnumStrings(const EnumStringsStorage<T, NumStrs, N, StrLen> &)
+    -> EnumStrings<T, NumStrs>;
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/Support/ScopedPrinter.h b/llvm/include/llvm/Support/ScopedPrinter.h
index e9e9903b2482a..2c3bb2e136cea 100644
--- a/llvm/include/llvm/Support/ScopedPrinter.h
+++ b/llvm/include/llvm/Support/ScopedPrinter.h
@@ -18,6 +18,7 @@
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/DataTypes.h"
 #include "llvm/Support/Endian.h"
+#include "llvm/Support/Enum.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
 #include <type_traits>
@@ -159,6 +160,15 @@ class LLVM_ABI ScopedPrinter {
 
   template <typename T> HexNumber hex(T Value) { return HexNumber(Value); }
 
+  template <typename T, typename TEnum, unsigned NumStrs>
+  void printEnum(StringRef Label, T Value,
+                 EnumStrings<TEnum, NumStrs> EnumValues) {
+    if (StringRef Name = EnumValues.toString(Value); !Name.empty())
+      printHex(Label, Name, Value);
+    else
+      printHex(Label, Value);
+  }
+
   template <typename T, typename TEnum>
   void printEnum(StringRef Label, T Value,
                  ArrayRef<EnumEntry<TEnum>> EnumValues) {
@@ -178,6 +188,34 @@ class LLVM_ABI ScopedPrinter {
       printHex(Label, Value);
   }
 
+  template <typename T, typename TFlag, unsigned NumStrs>
+  void printFlags(StringRef Label, T Value, EnumStrings<TFlag, NumStrs> Flags,
+                  TFlag EnumMask1 = {}, TFlag EnumMask2 = {},
+                  TFlag EnumMask3 = {}, ArrayRef<FlagEntry> ExtraFlags = {}) {
+    SmallVector<FlagEntry, 10> SetFlags(ExtraFlags);
+
+    for (const auto &Flag : Flags) {
+      if (Flag.value() == TFlag{})
+        continue;
+
+      TFlag EnumMask{};
+      if ((Flag.value() & EnumMask1) != TFlag{})
+        EnumMask = EnumMask1;
+      else if ((Flag.value() & EnumMask2) != TFlag{})
+        EnumMask = EnumMask2;
+      else if ((Flag.value() & EnumMask3) != TFlag{})
+        EnumMask = EnumMask3;
+      bool IsEnum = (Flag.value() & EnumMask) != TFlag{};
+      if ((!IsEnum && (Value & Flag.value()) == Flag.value()) ||
+          (IsEnum && (Value & EnumMask) == Flag.value())) {
+        SetFlags.emplace_back(Flag.name(), Flag.value());
+      }
+    }
+
+    llvm::sort(SetFlags, &flagName);
+    printFlagsImpl(Label, hex(Value), SetFlags);
+  }
+
   template <typename T, typename TFlag>
   void printFlags(StringRef Label, T Value, ArrayRef<EnumEntry<TFlag>> Flags,
                   TFlag EnumMask1 = {}, TFlag EnumMask2 = {},
@@ -206,6 +244,13 @@ class LLVM_ABI ScopedPrinter {
     printFlagsImpl(Label, hex(Value), SetFlags);
   }
 
+  template <typename T>
+  void printFlags(StringRef Label, T Value,
+                  SmallVectorImpl<FlagEntry> &SetFlags) {
+    llvm::sort(SetFlags, &flagName);
+    printFlagsImpl(Label, hex(Value), SetFlags);
+  }
+
   template <typename T> void printFlags(StringRef Label, T Value) {
     SmallVector<HexNumber, 10> SetFlags;
     uint64_t Flag = 1;
diff --git a/llvm/unittests/Support/ScopedPrinterTest.cpp b/llvm/unittests/Support/ScopedPrinterTest.cpp
index 280b3ee8c29c9..366e5b44231cc 100644
--- a/llvm/unittests/Support/ScopedPrinterTest.cpp
+++ b/llvm/unittests/Support/ScopedPrinterTest.cpp
@@ -231,6 +231,326 @@ Prefix2Prefix2|
   verifyScopedPrinter(ExpectedOut, PrintFunc);
 }
 
+TEST_F(ScopedPrinterTest, PrintEnumTable) {
+  auto PrintFunc = [](ScopedPrinter &W) {
+    constexpr EnumStringDef<int, 2> EnumListDef[] = {
+        {{"Name1", "AltName1"}, 1},
+        {{"Name2", "AltName2"}, 2},
+        {{"Name3", "AltName3"}, 3},
+        {{"Name4", "AltName4"}, 2},
+    };
+    constexpr auto EnumList = BUILD_ENUM_STRINGS(EnumListDef);
+    W.printEnum("Exists", EnumListDef[1].Value, EnumStrings(EnumList));
+    W.printEnum("DoesNotExist", 5, EnumStrings(EnumList));
+  };
+
+  const char *ExpectedOut = R"(Exists: Name2 (0x2)
+DoesNotExist: 0x5
+)";
+
+  const char *JSONExpectedOut = R"({
+  "Exists": {
+    "Name": "Name2",
+    "Value": 2
+  },
+  "DoesNotExist": 5
+})";
+  verifyAll(ExpectedOut, JSONExpectedOut, PrintFunc);
+}
+
+TEST_F(ScopedPrinterTest, PrintEnumFlag) {
+  auto PrintFunc = [](ScopedPrinter &W) {
+    constexpr EnumStringDef<uint16_t, 2> SingleBitFlagDefs[] = {
+        {{"Name0", "AltName0"}, 0},
+        {{"Name1", "AltName1"}, 1},
+        {{"Name2", "AltName2"}, 1 << 1},
+        {{"Name3", "AltName3"}, 1 << 2}};
+    constexpr auto SingleBitFlags = BUILD_ENUM_STRINGS(SingleBitFlagDefs);
+    constexpr EnumStringDef<uint16_t, 2> UnsortedFlagDefs[] = {
+        {{"C", "c"}, 1}, {{"B", "b"}, 1 << 1}, {{"A", "a"}, 1 << 2}};
+    constexpr auto UnsortedFlags = BUILD_ENUM_STRINGS(UnsortedFlagDefs);
+    constexpr EnumStringDef<uint16_t, 2> EnumFlagDefs[] = {
+        {{"FirstByte1", "First1"}, 0x1u},
+        {{"FirstByte2", "First2"}, 0x2u},
+        {{"FirstByte3", "First3"}, 0x3u},
+        {{"SecondByte1", "Second1"}, 0x10u},
+        {{"SecondByte2", "Second2"}, 0x20u},
+        {{"SecondByte3", "Second3"}, 0x30u},
+        {{"ThirdByte1", "Third1"}, 0x100u},
+        {{"ThirdByte2", "Third2"}, 0x200u},
+        {{"ThirdByte3", "Third3"}, 0x300u}};
+    constexpr auto EnumFlags = BUILD_ENUM_STRINGS(EnumFlagDefs);
+
+    constexpr EnumStringDef<BitmaskEnum, 2> ScopedFlagDefs[] = {
+        {{"F1", "AltF1"}, BitmaskEnum::F1},
+        {{"F2", "AltF2"}, BitmaskEnum::F2},
+    };
+    constexpr auto ScopedFlags = BUILD_ENUM_STRINGS(ScopedFlagDefs);
+
+    W.printFlags("ZeroFlag", 0, EnumStrings(SingleBitFlags));
+    W.printFlags("NoFlag", 1 << 3, EnumStrings(SingleBitFlags));
+    W.printFlags("Flag1", SingleBitFlagDefs[1].Value,
+                 EnumStrings(SingleBitFlags));
+    W.printFlags("Flag1&3", (1 << 2) + 1, EnumStrings(SingleBitFlags));
+
+    W.printFlags("ZeroFlagRaw", 0);
+    W.printFlags("NoFlagRaw", 1 << 3);
+    W.printFlags("Flag1Raw", SingleBitFlagDefs[1].Value);
+    W.printFlags("Flag1&3Raw", (1 << 2) + 1);
+
+    W.printFlags("FlagSorted", (1 << 2) + (1 << 1) + 1,
+                 EnumStrings(UnsortedFlags));
+
+    uint16_t NoBitMask = 0;
+    uint16_t FirstByteMask = 0xFu;
+    uint16_t SecondByteMask = 0xF0u;
+    uint16_t ThirdByteMask = 0xF00u;
+    W.printFlags("NoBitMask", 0xFFFu, EnumStrings(EnumFlags), NoBitMask);
+    W.printFlags("FirstByteMask", 0x3u, EnumStrings(EnumFlags), FirstByteMask);
+    W.printFlags("SecondByteMask", 0x30u, EnumStrings(EnumFlags),
+                 SecondByteMask);
+    W.printFlags("ValueOutsideMask", 0x1u, EnumStrings(EnumFlags),
+                 SecondByteMask);
+    W.printFlags("FirstSecondByteMask", 0xFFu, EnumStrings(EnumFlags),
+                 FirstByteMask, SecondByteMask);
+    W.printFlags("FirstSecondThirdByteMask", 0x333u, EnumStrings(EnumFlags),
+                 FirstByteMask, SecondByteMask, ThirdByteMask);
+    W.printFlags("BitmaskEnum::F1", BitmaskEnum::F1, EnumStrings(ScopedFlags));
+  };
+
+  const char *ExpectedOut = R"(ZeroFlag [ (0x0)
+]
+NoFlag [ (0x8)
+]
+Flag1 [ (0x1)
+  Name1 (0x1)
+]
+Flag1&3 [ (0x5)
+  Name1 (0x1)
+  Name3 (0x4)
+]
+ZeroFlagRaw [ (0x0)
+]
+NoFlagRaw [ (0x8)
+  0x8
+]
+Flag1Raw [ (0x1)
+  0x1
+]
+Flag1&3Raw [ (0x5)
+  0x1
+  0x4
+]
+FlagSorted [ (0x7)
+  A (0x4)
+  B (0x2)
+  C (0x1)
+]
+NoBitMask [ (0xFFF)
+  FirstByte1 (0x1)
+  FirstByte2 (0x2)
+  FirstByte3 (0x3)
+  SecondByte1 (0x10)
+  SecondByte2 (0x20)
+  SecondByte3 (0x30)
+  ThirdByte1 (0x100)
+  ThirdByte2 (0x200)
+  ThirdByte3 (0x300)
+]
+FirstByteMask [ (0x3)
+  FirstByte3 (0x3)
+]
+SecondByteMask [ (0x30)
+  SecondByte3 (0x30)
+]
+ValueOutsideMask [ (0x1)
+  FirstByte1 (0x1)
+]
+FirstSecondByteMask [ (0xFF)
+]
+FirstSecondThirdByteMask [ (0x333)
+  FirstByte3 (0x3)
+  SecondByte3 (0x30)
+  ThirdByte3 (0x300)
+]
+BitmaskEnum::F1 [ (0x1)
+  F1 (0x1)
+]
+)";
+
+  const char *JSONExpectedOut = R"({
+  "ZeroFlag": {
+    "Value": 0,
+    "Flags": []
+  },
+  "NoFlag": {
+    "Value": 8,
+    "Flags": []
+  },
+  "Flag1": {
+    "Value": 1,
+    "Flags": [
+      {
+        "Name": "Name1",
+        "Value": 1
+      }
+    ]
+  },
+  "Flag1&3": {
+    "Value": 5,
+    "Flags": [
+      {
+        "Name": "Name1",
+        "Value": 1
+      },
+      {
+        "Name": "Name3",
+        "Value": 4
+      }
+    ]
+  },
+  "ZeroFlagRaw": {
+    "Value": 0,
+    "Flags": []
+  },
+  "NoFlagRaw": {
+    "Value": 8,
+    "Flags": [
+      8
+    ]
+  },
+  "Flag1Raw": {
+    "Value": 1,
+    "Flags": [
+      1
+    ]
+  },
+  "Flag1&3Raw": {
+    "Value": 5,
+    "Flags": [
+      1,
+      4
+    ]
+  },
+  "FlagSorted": {
+    "Value": 7,
+    "Flags": [
+      {
+        "Name": "A",
+        "Value": 4
+      },
+      {
+        "Name": "B",
+        "Value": 2
+      },
+      {
+        "Name": "C",
+        "Value": 1
+      }
+    ]
+  },
+  "NoBitMask": {
+    "Value": 4095,
+    "Flags": [
+      {
+        "Name": "FirstByte1",
+        "Value": 1
+      },
+      {
+        "Name": "FirstByte2",
+        "Value": 2
+      },
+      {
+        "Name": "FirstByte3",
+        "Value": 3
+      },
+      {
+        "Name": "SecondByte1",
+        "Value": 16
+      },
+      {
+        "Name": "SecondByte2",
+        "Value": 32
+      },
+      {
+        "Name": "SecondByte3",
+        "Value": 48
+      },
+      {
+        "Name": "ThirdByte1",
+        "Value": 256
+      },
+      {
+        "Name": "ThirdByte2",
+        "Value": 512
+      },
+      {
+        "Name": "ThirdByte3",
+        "Value": 768
+      }
+    ]
+  },
+  "FirstByteMask": {
+    "Value": 3,
+    "Flags": [
+      {
+        "Name": "FirstByte3",
+        "Value": 3
+      }
+    ]
+  },
+  "SecondByteMask": {
+    "Value": 48,
+    "Flags": [
+      {
+        "Name": "SecondByte3",
+        "Value": 48
+      }
+    ]
+  },
+  "ValueOutsideMask": {
+    "Value": 1,
+    "Flags": [
+      {
+        "Name": "FirstByte1",
+        "Value": 1
+      }
+    ]
+  },
+  "FirstSecondByteMask": {
+    "Value": 255,
+    "Flags": []
+  },
+  "FirstSecondThirdByteMask": {
+    "Value": 819,
+    "Flags": [
+      {
+        "Name": "FirstByte3",
+        "Value": 3
+      },
+      {
+        "Name": "SecondByte3",
+        "Value": 48
+      },
+      {
+        "Name": "ThirdByte3",
+        "Value": 768
+      }
+    ]
+  },
+  "BitmaskEnum::F1": {
+    "Value": 1,
+    "Flags": [
+      {
+        "Name": "F1",
+        "Value": 1
+      }
+    ]
+  }
+})";
+  verifyAll(ExpectedOut, JSONExpectedOut, PrintFunc);
+}
+
 TEST_F(ScopedPrinterTest, PrintEnum) {
   auto PrintFunc = [](ScopedPrinter &W) {
     const EnumEntry<int> EnumList[] = {{"Name1", "AltName1", 1},

>From 74b1449a2d28da8d1bc4b19dc641bc7809c7e09d Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Sat, 6 Jun 2026 14:20:56 +0000
Subject: [PATCH 2/2] gcc compatibility

Created using spr 1.3.8-wip
---
 llvm/include/llvm/Support/Enum.h | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/include/llvm/Support/Enum.h b/llvm/include/llvm/Support/Enum.h
index ccab45769bb5e..b41b92cba032a 100644
--- a/llvm/include/llvm/Support/Enum.h
+++ b/llvm/include/llvm/Support/Enum.h
@@ -95,11 +95,13 @@ struct EnumStringsStorage {
     for (unsigned i = 0; i < N; i++) {
       Data[i].Value = Entries[i].Value;
       for (unsigned j = 0; j < NumStrs; j++) {
-        unsigned StrOff = offsetof(EnumStringsStorage, Strs[StrIdx]) -
-                          offsetof(EnumStringsStorage, Data[i]);
+        unsigned StrOff = offsetof(EnumStringsStorage, Strs) + StrIdx;
+        unsigned DataOff = offsetof(EnumStringsStorage, Data) +
+                           i * sizeof(EnumString<T, NumStrs>);
+        assert(StrOff - DataOff < UINT16_MAX && "enum string table too large");
         std::string_view Name(Entries[i].Names[j]);
         Data[i].NameSize[j] = Name.size();
-        Data[i].NameOff[j] = uint16_t(StrOff);
+        Data[i].NameOff[j] = uint16_t(StrOff - DataOff);
         for (char C : Name)
           Strs[StrIdx++] = C;
       }
@@ -115,7 +117,7 @@ struct EnumStringsStorage {
 
 template <typename T, unsigned NumStrs = 1> class EnumStrings {
 public:
-  using EnumString = EnumString<T, NumStrs>;
+  using EnumString = ::llvm::EnumString<T, NumStrs>;
 
   template <size_t N, size_t StrLen>
   EnumStrings(const EnumStringsStorage<T, NumStrs, N, StrLen> &Table)



More information about the llvm-commits mailing list