[llvm] [llvm][TableGen] Split builtin lookup tables (PR #202644)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 07:01:01 PDT 2026


https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202644

Store builtin string-table offsets in a four-byte BuiltinNameEntry array
and intrinsic IDs in a parallel uint16_t array. Use uint16_t offsets and
counts in TargetEntry, reducing TargetEntry from 48 to 40 bytes on LP64.
Keep a numeric binary-search index so selecting the parallel intrinsic ID
does not require reconstructing an index from an iterator.

Add static assertions for BuiltinNameEntry and TargetEntry sizes, string
offset width, intrinsic ID width, table offset width, target builtin
count, and parallel table lengths.

In an arm64 Release build, this reduces Intrinsics.cpp.o from 934,944 to
915,648 bytes (-19,296, -2.064%) and libLLVMCore.a from 6,595,760 to
6,576,464 bytes (-19,296, -0.293%). A benchmark executable linked with
otherwise identical archives shrinks from 5,231,320 to 5,196,824 bytes
(-34,496, -0.659%).

Across 111 alternating paired process runs of the existing
GetIntrinsicForClangBuiltin benchmark, the mixed-target case changed by
+0.168% with a 95% confidence interval of [-1.076%, +1.428%]. The
Hexagon-first case improved by 3.099% with a 95% confidence interval of
[-3.867%, -2.325%].

Tests:
- ninja check-llvm-tablegen (420 tests)
- IntrinsicsTest (16 tests)
- llvm/test/TableGen/intrinsic-builtin-table-layout.td

Work towards #202616

>From 0ebd2011555c5175b6053ca257df0de62db53c83 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Tue, 9 Jun 2026 08:00:48 -0400
Subject: [PATCH] [llvm][TableGen] Split builtin lookup tables

Store builtin string-table offsets in a four-byte BuiltinNameEntry array
and intrinsic IDs in a parallel uint16_t array. Use uint16_t offsets and
counts in TargetEntry, reducing TargetEntry from 48 to 40 bytes on LP64.
Keep a numeric binary-search index so selecting the parallel intrinsic ID
does not require reconstructing an index from an iterator.

Add static assertions for BuiltinNameEntry and TargetEntry sizes, string
offset width, intrinsic ID width, table offset width, target builtin
count, and parallel table lengths.

In an arm64 Release build, this reduces Intrinsics.cpp.o from 934,944 to
915,648 bytes (-19,296, -2.064%) and libLLVMCore.a from 6,595,760 to
6,576,464 bytes (-19,296, -0.293%). A benchmark executable linked with
otherwise identical archives shrinks from 5,231,320 to 5,196,824 bytes
(-34,496, -0.659%).

Across 111 alternating paired process runs of the existing
GetIntrinsicForClangBuiltin benchmark, the mixed-target case changed by
+0.168% with a 95% confidence interval of [-1.076%, +1.428%]. The
Hexagon-first case improved by 3.099% with a 95% confidence interval of
[-3.867%, -2.325%].

Tests:
- ninja check-llvm-tablegen (420 tests)
- IntrinsicsTest (16 tests)
- llvm/test/TableGen/intrinsic-builtin-table-layout.td
---
 .../intrinsic-builtin-table-layout.td         |  59 +++++++++
 .../utils/TableGen/Basic/IntrinsicEmitter.cpp | 119 ++++++++++++++----
 2 files changed, 155 insertions(+), 23 deletions(-)
 create mode 100644 llvm/test/TableGen/intrinsic-builtin-table-layout.td

diff --git a/llvm/test/TableGen/intrinsic-builtin-table-layout.td b/llvm/test/TableGen/intrinsic-builtin-table-layout.td
new file mode 100644
index 0000000000000..12d3df08b956e
--- /dev/null
+++ b/llvm/test/TableGen/intrinsic-builtin-table-layout.td
@@ -0,0 +1,59 @@
+// RUN: llvm-tblgen -gen-intrinsic-impl -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS | FileCheck %s
+
+include "llvm/IR/Intrinsics.td"
+
+def int_zeta : Intrinsic<[]>, ClangBuiltin<"__builtin_alpha">;
+def int_alpha : Intrinsic<[]>, ClangBuiltin<"__builtin_beta">;
+
+let TargetPrefix = "other" in
+def int_other_zeta : Intrinsic<[]>, ClangBuiltin<"__builtin_other_one">;
+
+let TargetPrefix = "target" in {
+  def int_target_zeta : Intrinsic<[]>, ClangBuiltin<"__builtin_target_one">;
+  def int_target_alpha : Intrinsic<[]>, ClangBuiltin<"__builtin_target_two">;
+}
+
+// CHECK-LABEL: struct BuiltinNameEntry {
+// CHECK-NEXT: uint32_t StrTabOffset;
+// CHECK-NOT: IntrinsicID
+// CHECK: };
+// CHECK: static_assert(sizeof(BuiltinNameEntry) == sizeof(uint32_t),
+// CHECK: static_assert(sizeof(BuiltinNamesStorage) <=
+// CHECK-LABEL: static constexpr BuiltinNameEntry BuiltinNameEntries[] = {
+// CHECK-NEXT: // Target independent builtins.
+// CHECK-NEXT: {{    \{[0-9]+\}, // __builtin_alpha}}
+// CHECK-NEXT: {{    \{[0-9]+\}, // __builtin_beta}}
+// CHECK-NEXT: // Builtins for other.
+// CHECK-NEXT: {{    \{[0-9]+\}, // __builtin_other_one}}
+// CHECK-NEXT: // Builtins for target.
+// CHECK-NEXT: {{    \{[0-9]+\}, // __builtin_target_one}}
+// CHECK-NEXT: {{    \{[0-9]+\}, // __builtin_target_two}}
+// CHECK-NEXT: };
+// CHECK: static_assert(2 <= uint16_t(-1),
+// CHECK-LABEL: static constexpr uint16_t BuiltinIntrinsicIDs[] = {
+// CHECK-NEXT: zeta, // __builtin_alpha
+// CHECK-NEXT: alpha, // __builtin_beta
+// CHECK-NEXT: other_zeta, // __builtin_other_one
+// CHECK-NEXT: target_zeta, // __builtin_target_one
+// CHECK-NEXT: target_alpha, // __builtin_target_two
+// CHECK-NEXT: };
+// CHECK: static_assert(target_zeta <= uint16_t(-1),
+// CHECK: static_assert(std::size(BuiltinNameEntries) ==
+// CHECK: static_assert(std::size(BuiltinIntrinsicIDs) <=
+// CHECK-LABEL: struct TargetEntry {
+// CHECK-NEXT: StringLiteral TargetPrefix;
+// CHECK-NEXT: uint16_t EntryOffset;
+// CHECK-NEXT: uint16_t NumEntries;
+// CHECK-NEXT: StringLiteral CommonPrefix;
+// CHECK: static_assert(sizeof(TargetEntry) ==
+// CHECK: 2 * sizeof(StringLiteral) + sizeof(void *),
+// CHECK: {"other", 2, 1, "__builtin_other_one"},
+// CHECK: {"target", 3, 2, "__builtin_target_"},
+// CHECK: ArrayRef<BuiltinNameEntry> Names(BuiltinNameEntries + 0, 2);
+// CHECK: size_t First = 0;
+// CHECK: if (Names[Middle] < Suffix) {
+// CHECK: return static_cast<ID>(BuiltinIntrinsicIDs[0 + First]);
+// CHECK: ArrayRef<BuiltinNameEntry> TargetNames(BuiltinNameEntries + TI->EntryOffset,
+// CHECK-NEXT: TI->NumEntries);
+// CHECK: if (TargetNames[Middle] < BuiltinName) {
+// CHECK: BuiltinIntrinsicIDs[TI->EntryOffset + First]);
diff --git a/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp b/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
index 02b2cd9850997..496bd405ed89a 100644
--- a/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/IntrinsicEmitter.cpp
@@ -892,11 +892,15 @@ void IntrinsicEmitter::EmitIntrinsicToBuiltinMap(
   using BIMEntryTy =
       std::pair<std::map<StringRef, StringRef>, std::optional<StringRef>>;
   std::map<StringRef, BIMEntryTy> BuiltinMap;
+  // EmitEnum assigns IDs in CodeGenIntrinsicTable order, so the last builtin
+  // encountered has the greatest intrinsic enum value among all builtins.
+  StringRef MaxBuiltinIntrinsicEnumName;
 
   for (const CodeGenIntrinsic &Int : Ints) {
     StringRef BuiltinName = IsClang ? Int.ClangBuiltinName : Int.MSBuiltinName;
     if (BuiltinName.empty())
       continue;
+    MaxBuiltinIntrinsicEnumName = Int.EnumName;
     // Get the map for this target prefix.
     auto &[Map, CommonPrefix] = BuiltinMap[Int.TargetPrefix];
 
@@ -952,21 +956,30 @@ Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
     Table.EmitStringTableDef(OS, "BuiltinNames");
 
     OS << R"(
-  struct BuiltinEntry {
-    ID IntrinsicID;
-    unsigned StrTabOffset;
+  struct BuiltinNameEntry {
+    uint32_t StrTabOffset;
+
     const char *getName() const { return BuiltinNames[StrTabOffset].data(); }
     bool operator<(StringRef RHS) const {
       return strncmp(getName(), RHS.data(), RHS.size()) < 0;
     }
   };
-
+  static_assert(sizeof(BuiltinNameEntry) == sizeof(uint32_t),
+                "BuiltinNameEntry must remain packed");
+  static_assert(sizeof(BuiltinNamesStorage) <=
+                    uint64_t(uint32_t(-1)) + 1,
+                "builtin name offsets do not fit in uint32_t");
 )";
   }
 
-  // Emit a per target table of bultin names.
+  // Emit builtin name offsets for all targets in map order.
   bool HasTargetIndependentBuiltins = false;
   StringRef TargetIndepndentCommonPrefix;
+  size_t TargetIndependentIntrinsicIDOffset = 0;
+  size_t TargetIndependentBuiltinCount = 0;
+  size_t IntrinsicIDOffset = 0;
+  size_t MaxTargetBuiltinCount = 0;
+  OS << "  static constexpr BuiltinNameEntry BuiltinNameEntries[] = {\n";
   for (const auto &[TargetPrefix, Entry] : BuiltinMap) {
     const auto &[Map, CommonPrefix] = Entry;
     if (!TargetPrefix.empty()) {
@@ -975,18 +988,42 @@ Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
       OS << "  // Target independent builtins.\n";
       HasTargetIndependentBuiltins = true;
       TargetIndepndentCommonPrefix = *CommonPrefix;
+      TargetIndependentIntrinsicIDOffset = IntrinsicIDOffset;
+      TargetIndependentBuiltinCount = Map.size();
     }
 
-    // Emit the builtin table for this target prefix.
-    OS << formatv("  static constexpr BuiltinEntry {}Names[] = {{\n",
-                  TargetPrefix);
     for (const auto &[BuiltinName, EnumName] : Map) {
       StringRef Suffix = BuiltinName.substr(CommonPrefix->size());
-      OS << formatv("    {{{}, {}}, // {}\n", EnumName,
-                    *Table.GetStringOffset(Suffix), BuiltinName);
+      OS << "    {" << *Table.GetStringOffset(Suffix) << "}, // "
+         << BuiltinName << "\n";
     }
-    OS << formatv("  }; // {}Names\n\n", TargetPrefix);
+    if (!TargetPrefix.empty())
+      MaxTargetBuiltinCount = std::max(MaxTargetBuiltinCount, Map.size());
+    IntrinsicIDOffset += Map.size();
   }
+  OS << "  };\n";
+  OS << formatv(R"(  static_assert({0} <= uint16_t(-1),
+                "target builtin count does not fit in TargetEntry");
+)",
+                MaxTargetBuiltinCount);
+
+  OS << "  static constexpr uint16_t BuiltinIntrinsicIDs[] = {\n";
+  for (const auto &[TargetPrefix, Entry] : BuiltinMap)
+    for (const auto &[BuiltinName, EnumName] : Entry.first)
+      OS << "    " << EnumName << ", // " << BuiltinName << "\n";
+  OS << "  };\n";
+  OS << formatv(R"(  static_assert({0} <= uint16_t(-1),
+                "builtin intrinsic IDs do not fit in uint16_t");
+)",
+                MaxBuiltinIntrinsicEnumName);
+  OS << R"(  static_assert(std::size(BuiltinNameEntries) ==
+                    std::size(BuiltinIntrinsicIDs),
+                "builtin name and ID tables must have the same size");
+  static_assert(std::size(BuiltinIntrinsicIDs) <=
+                    uint32_t(uint16_t(-1)) + 1,
+                "builtin ID offsets do not fit in TargetEntry");
+
+)";
 
   // After emitting the builtin tables for all targets, emit a lookup table for
   // all targets. We will use binary search, similar to the table for builtin
@@ -994,22 +1031,29 @@ Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
   OS << R"(
   struct TargetEntry {
     StringLiteral TargetPrefix;
-    ArrayRef<BuiltinEntry> Names;
+    uint16_t EntryOffset;
+    uint16_t NumEntries;
     StringLiteral CommonPrefix;
     bool operator<(StringRef RHS) const {
       return TargetPrefix < RHS;
     };
   };
+  static_assert(sizeof(TargetEntry) ==
+                    2 * sizeof(StringLiteral) + sizeof(void *),
+                "TargetEntry must remain packed");
   static constexpr TargetEntry TargetTable[] = {
 )";
-
+  IntrinsicIDOffset = 0;
   for (const auto &[TargetPrefix, Entry] : BuiltinMap) {
     const auto &[Map, CommonPrefix] = Entry;
-    if (TargetPrefix.empty())
+    if (TargetPrefix.empty()) {
+      IntrinsicIDOffset += Map.size();
       continue;
-    OS << formatv(R"(    {{"{0}", {0}Names, "{1}"},)", TargetPrefix,
-                  CommonPrefix)
+    }
+    OS << formatv(R"(    {{"{0}", {2}, {3}, "{1}"},)",
+                  TargetPrefix, CommonPrefix, IntrinsicIDOffset, Map.size())
        << "\n";
+    IntrinsicIDOffset += Map.size();
   }
   OS << "  };\n";
 
@@ -1022,12 +1066,27 @@ Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
   // if for the lookup in the target specific table.
   StringRef Suffix = BuiltinName;
   if (Suffix.consume_front("{}")) {{
-    auto II = lower_bound(Names, Suffix);
-    if (II != std::end(Names) && II->getName() == Suffix)
-      return II->IntrinsicID;
+    ArrayRef<BuiltinNameEntry> Names(BuiltinNameEntries + {}, {});
+    size_t First = 0;
+    size_t Count = Names.size();
+    while (Count != 0) {{
+      size_t Step = Count / 2;
+      size_t Middle = First + Step;
+      if (Names[Middle] < Suffix) {{
+        First = Middle + 1;
+        Count -= Step + 1;
+      } else {{
+        Count = Step;
+      }
+    }
+    if (First != Names.size() && Names[First].getName() == Suffix)
+      return static_cast<ID>(BuiltinIntrinsicIDs[{} + First]);
   }
 )",
-                  TargetIndepndentCommonPrefix);
+                  TargetIndepndentCommonPrefix,
+                  TargetIndependentIntrinsicIDOffset,
+                  TargetIndependentBuiltinCount,
+                  TargetIndependentIntrinsicIDOffset);
   }
 
   // If a target independent builtin was not found, lookup the target specific.
@@ -1039,10 +1098,24 @@ Intrinsic::getIntrinsicFor{}Builtin(StringRef TargetPrefix,
   // consume_front.
   if (!BuiltinName.consume_front(TI->CommonPrefix))
     return not_intrinsic;
-  auto II = lower_bound(TI->Names, BuiltinName);
-  if (II == std::end(TI->Names) || II->getName() != BuiltinName)
+  ArrayRef<BuiltinNameEntry> TargetNames(BuiltinNameEntries + TI->EntryOffset,
+                                         TI->NumEntries);
+  size_t First = 0;
+  size_t Count = TargetNames.size();
+  while (Count != 0) {
+    size_t Step = Count / 2;
+    size_t Middle = First + Step;
+    if (TargetNames[Middle] < BuiltinName) {
+      First = Middle + 1;
+      Count -= Step + 1;
+    } else {
+      Count = Step;
+    }
+  }
+  if (First == TargetNames.size() ||
+      TargetNames[First].getName() != BuiltinName)
     return not_intrinsic;
-  return II->IntrinsicID;
+  return static_cast<ID>(BuiltinIntrinsicIDs[TI->EntryOffset + First]);
 }
 )";
 }



More information about the llvm-commits mailing list