[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