Skip to content

[utils][TableGen] Handle versions on clause/directive spellings #143021

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 6, 2025

Conversation

kparzysz
Copy link
Contributor

@kparzysz kparzysz commented Jun 5, 2025

In "getDirectiveName(Kind, Version)", return the spelling that corresponds to Version, and in "getDirectiveKindAndVersions(Name)" return the pair {Kind, VersionRange}, where VersionRange contains the minimum and the maximum versions that allow "Name" as a spelling. This applies to clauses as well. In general it applies to classes that have spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires a runtime search (which can fail in a general case). To avoid generating the search function inline, a small additional component of llvm/Frontent was added: LLVMFrontendDirective. The corresponding header file also defines C++ classes "Spelling" and "VersionRange", which are used in TableGen/DirectiveEmitter as well.

For background information see
https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507

In "get<lang>DirectiveName(Kind, Version)", return the spelling that
corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)"
return the pair {Kind, VersionRange}, where VersionRange contains the
minimum and the maximum versions that allow "Name" as a spelling.
This applies to clauses as well. In general it applies to classes that
have spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires
a runtime search (which can fail in a general case). To avoid generating
the search function inline, a small additional component of llvm/Frontent
was added: LLVMFrontendDirective. The corresponding header file also
defines C++ classes "Spelling" and "VersionRange", which are used in
TableGen/DirectiveEmitter as well.

For background information see
https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507
@kparzysz
Copy link
Contributor Author

kparzysz commented Jun 5, 2025

Recreation of accidentally destroyed #141766.

@llvmbot llvmbot added tablegen flang:openmp openacc clang:openmp OpenMP related changes to Clang labels Jun 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 5, 2025

@llvm/pr-subscribers-openacc

@llvm/pr-subscribers-flang-openmp

Author: Krzysztof Parzyszek (kparzysz)

Changes

In "get<lang>DirectiveName(Kind, Version)", return the spelling that corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)" return the pair {Kind, VersionRange}, where VersionRange contains the minimum and the maximum versions that allow "Name" as a spelling. This applies to clauses as well. In general it applies to classes that have spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires a runtime search (which can fail in a general case). To avoid generating the search function inline, a small additional component of llvm/Frontent was added: LLVMFrontendDirective. The corresponding header file also defines C++ classes "Spelling" and "VersionRange", which are used in TableGen/DirectiveEmitter as well.

For background information see
https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507


Patch is 26.52 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143021.diff

10 Files Affected:

  • (added) llvm/include/llvm/Frontend/Directive/Spelling.h (+39)
  • (modified) llvm/include/llvm/TableGen/DirectiveEmitter.h (+8-17)
  • (modified) llvm/lib/Frontend/CMakeLists.txt (+1)
  • (added) llvm/lib/Frontend/Directive/CMakeLists.txt (+6)
  • (added) llvm/lib/Frontend/Directive/Spelling.cpp (+31)
  • (modified) llvm/lib/Frontend/OpenACC/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Frontend/OpenMP/CMakeLists.txt (+1)
  • (modified) llvm/test/TableGen/directive1.td (+20-14)
  • (modified) llvm/test/TableGen/directive2.td (+12-12)
  • (modified) llvm/utils/TableGen/Basic/DirectiveEmitter.cpp (+93-53)
diff --git a/llvm/include/llvm/Frontend/Directive/Spelling.h b/llvm/include/llvm/Frontend/Directive/Spelling.h
new file mode 100644
index 0000000000000..3ba0ae2296535
--- /dev/null
+++ b/llvm/include/llvm/Frontend/Directive/Spelling.h
@@ -0,0 +1,39 @@
+//===-- Spelling.h ------------------------------------------------ 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_FRONTEND_DIRECTIVE_SPELLING_H
+#define LLVM_FRONTEND_DIRECTIVE_SPELLING_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator_range.h"
+
+#include <limits>
+
+namespace llvm::directive {
+
+struct VersionRange {
+  static constexpr int MaxValue = std::numeric_limits<int>::max();
+  int Min = 1;
+  int Max = MaxValue;
+};
+
+inline bool operator<(const VersionRange &A, const VersionRange &B) {
+  if (A.Min != B.Min)
+    return A.Min < B.Min;
+  return A.Max < B.Max;
+}
+
+struct Spelling {
+  StringRef Name;
+  VersionRange Versions;
+};
+
+StringRef FindName(llvm::iterator_range<const Spelling *>, unsigned Version);
+
+} // namespace llvm::directive
+
+#endif // LLVM_FRONTEND_DIRECTIVE_SPELLING_H
diff --git a/llvm/include/llvm/TableGen/DirectiveEmitter.h b/llvm/include/llvm/TableGen/DirectiveEmitter.h
index dc2f75083ec0d..a2256716a7c8b 100644
--- a/llvm/include/llvm/TableGen/DirectiveEmitter.h
+++ b/llvm/include/llvm/TableGen/DirectiveEmitter.h
@@ -17,6 +17,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Frontend/Directive/Spelling.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/TableGen/Record.h"
 #include <algorithm>
@@ -113,29 +114,19 @@ class Versioned {
   constexpr static int IntWidth = 8 * sizeof(int);
 };
 
-// Range of specification versions: [Min, Max]
-// Default value: all possible versions.
-// This is the same structure as the one emitted into the generated sources.
-#define STRUCT_VERSION_RANGE                                                   \
-  struct VersionRange {                                                        \
-    int Min = 1;                                                               \
-    int Max = 0x7fffffff;                                                      \
-  }
-
-STRUCT_VERSION_RANGE;
-
 class Spelling : public Versioned {
 public:
-  using Value = std::pair<StringRef, VersionRange>;
+  using Value = llvm::directive::Spelling;
 
   Spelling(const Record *Def) : Def(Def) {}
 
   StringRef getText() const { return Def->getValueAsString("spelling"); }
-  VersionRange getVersions() const {
-    return VersionRange{getMinVersion(Def), getMaxVersion(Def)};
+  llvm::directive::VersionRange getVersions() const {
+    return llvm::directive::VersionRange{getMinVersion(Def),
+                                         getMaxVersion(Def)};
   }
 
-  Value get() const { return std::make_pair(getText(), getVersions()); }
+  Value get() const { return Value{getText(), getVersions()}; }
 
 private:
   const Record *Def;
@@ -177,9 +168,9 @@ class BaseRecord {
     // are added.
     Spelling::Value Oldest{"not found", {/*Min=*/INT_MAX, 0}};
     for (auto V : getSpellings())
-      if (V.second.Min < Oldest.second.Min)
+      if (V.Versions.Min < Oldest.Versions.Min)
         Oldest = V;
-    return Oldest.first;
+    return Oldest.Name;
   }
 
   // Returns the name of the directive formatted for output. Whitespace are
diff --git a/llvm/lib/Frontend/CMakeLists.txt b/llvm/lib/Frontend/CMakeLists.txt
index b305ce7d771ce..3b31e6f8dec96 100644
--- a/llvm/lib/Frontend/CMakeLists.txt
+++ b/llvm/lib/Frontend/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(Atomic)
+add_subdirectory(Directive)
 add_subdirectory(Driver)
 add_subdirectory(HLSL)
 add_subdirectory(OpenACC)
diff --git a/llvm/lib/Frontend/Directive/CMakeLists.txt b/llvm/lib/Frontend/Directive/CMakeLists.txt
new file mode 100644
index 0000000000000..a567e1affb171
--- /dev/null
+++ b/llvm/lib/Frontend/Directive/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_llvm_component_library(LLVMFrontendDirective
+  Spelling.cpp
+
+  LINK_COMPONENTS
+  Support
+)
diff --git a/llvm/lib/Frontend/Directive/Spelling.cpp b/llvm/lib/Frontend/Directive/Spelling.cpp
new file mode 100644
index 0000000000000..c808e625ee4fd
--- /dev/null
+++ b/llvm/lib/Frontend/Directive/Spelling.cpp
@@ -0,0 +1,31 @@
+//===-- Spelling.cpp ---------------------------------------------- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/Directive/Spelling.h"
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <cassert>
+
+llvm::StringRef llvm::directive::FindName(
+    llvm::iterator_range<const llvm::directive::Spelling *> Range,
+    unsigned Version) {
+  assert(llvm::isInt<8 * sizeof(int)>(Version) && "Version value out of range");
+
+  int V = Version;
+  Spelling Tmp{StringRef(), {V, V}};
+  auto F =
+      llvm::lower_bound(Range, Tmp, [](const Spelling &A, const Spelling &B) {
+        return A.Versions < B.Versions;
+      });
+  if (F != Range.end())
+    return F->Name;
+  return StringRef();
+}
diff --git a/llvm/lib/Frontend/OpenACC/CMakeLists.txt b/llvm/lib/Frontend/OpenACC/CMakeLists.txt
index f352014978690..4664b71407c48 100644
--- a/llvm/lib/Frontend/OpenACC/CMakeLists.txt
+++ b/llvm/lib/Frontend/OpenACC/CMakeLists.txt
@@ -9,5 +9,5 @@ add_llvm_component_library(LLVMFrontendOpenACC
   acc_gen
 )
 
-target_link_libraries(LLVMFrontendOpenACC LLVMSupport)
+target_link_libraries(LLVMFrontendOpenACC LLVMSupport LLVMFrontendDirective)
 
diff --git a/llvm/lib/Frontend/OpenMP/CMakeLists.txt b/llvm/lib/Frontend/OpenMP/CMakeLists.txt
index 35c607866a94e..5bf15ca3a8991 100644
--- a/llvm/lib/Frontend/OpenMP/CMakeLists.txt
+++ b/llvm/lib/Frontend/OpenMP/CMakeLists.txt
@@ -23,4 +23,5 @@ add_llvm_component_library(LLVMFrontendOpenMP
   BitReader
   FrontendOffloading
   FrontendAtomic
+  FrontendDirective
   )
diff --git a/llvm/test/TableGen/directive1.td b/llvm/test/TableGen/directive1.td
index 8196a30d03df4..1c8da26f50f4b 100644
--- a/llvm/test/TableGen/directive1.td
+++ b/llvm/test/TableGen/directive1.td
@@ -54,6 +54,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  #include "llvm/ADT/ArrayRef.h"
 // CHECK-NEXT:  #include "llvm/ADT/BitmaskEnum.h"
 // CHECK-NEXT:  #include "llvm/ADT/StringRef.h"
+// CHECK-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // CHECK-NEXT:  #include "llvm/Support/Compiler.h"
 // CHECK-NEXT:  #include <cstddef>
 // CHECK-NEXT:  #include <utility>
@@ -63,8 +64,6 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 // CHECK-EMPTY:
-// CHECK-NEXT:  struct VersionRange { int Min = 1; int Max = 0x7fffffff; };
-// CHECK-EMPTY:
 // CHECK-NEXT:  enum class Association {
 // CHECK-NEXT:    Block,
 // CHECK-NEXT:    Declaration,
@@ -126,14 +125,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  constexpr auto TDLCV_valc = AKind::TDLCV_valc;
 // CHECK-EMPTY:
 // CHECK-NEXT:  // Enumeration helper functions
-// CHECK-NEXT:  LLVM_ABI std::pair<Directive, VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Directive, directive::VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
 // CHECK-NEXT:  inline Directive getTdlDirectiveKind(StringRef Str) {
 // CHECK-NEXT:   return getTdlDirectiveKindAndVersions(Str).first;
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ABI StringRef getTdlDirectiveName(Directive D, unsigned Ver = 0);
 // CHECK-EMPTY:
-// CHECK-NEXT:  LLVM_ABI std::pair<Clause, VersionRange> getTdlClauseKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Clause, directive::VersionRange> getTdlClauseKindAndVersions(StringRef Str);
 // CHECK-EMPTY:
 // CHECK-NEXT:  inline Clause getTdlClauseKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlClauseKindAndVersions(Str).first;
@@ -320,17 +319,18 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL:       #ifdef GEN_DIRECTIVES_IMPL
 // IMPL-NEXT:  #undef GEN_DIRECTIVES_IMPL
 // IMPL-EMPTY:
+// IMPL-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // IMPL-NEXT:  #include "llvm/Support/ErrorHandling.h"
 // IMPL-NEXT:  #include <utility>
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::tdl::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Directive, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::directive::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Directive, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("dira", {TDLD_dira, All})
 // IMPL-NEXT:     .Default({TDLD_dira, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLD_dira:
 // IMPL-NEXT:        return "dira";
@@ -338,23 +338,29 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Directive kind");
 // IMPL-NEXT:  }
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::tdl::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Clause, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::directive::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Clause, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("clausea", {TDLC_clausea, All})
 // IMPL-NEXT:     .Case("clauseb", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clausec", {TDLC_clausec, All})
+// IMPL-NEXT:     .Case("ccccccc", {TDLC_clausec, All})
 // IMPL-NEXT:     .Default({TDLC_clauseb, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLC_clausea:
 // IMPL-NEXT:        return "clausea";
 // IMPL-NEXT:      case TDLC_clauseb:
 // IMPL-NEXT:        return "clauseb";
-// IMPL-NEXT:      case TDLC_clausec:
-// IMPL-NEXT:        return "clausec";
+// IMPL-NEXT:      case TDLC_clausec: {
+// IMPL-NEXT:        static const llvm::directive::Spelling TDLC_clausec_spellings[] = {
+// IMPL-NEXT:            {"clausec", {1, 2147483647}},
+// IMPL-NEXT:            {"ccccccc", {1, 2147483647}},
+// IMPL-NEXT:        };
+// IMPL-NEXT:        return llvm::directive::FindName(TDLC_clausec_spellings, Version);
+// IMPL-NEXT:      }
 // IMPL-NEXT:    }
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Clause kind");
 // IMPL-NEXT:  }
diff --git a/llvm/test/TableGen/directive2.td b/llvm/test/TableGen/directive2.td
index ead6aa2637b76..3a64bb3900a31 100644
--- a/llvm/test/TableGen/directive2.td
+++ b/llvm/test/TableGen/directive2.td
@@ -47,6 +47,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-EMPTY:
 // CHECK-NEXT:  #include "llvm/ADT/ArrayRef.h"
 // CHECK-NEXT:  #include "llvm/ADT/StringRef.h"
+// CHECK-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // CHECK-NEXT:  #include "llvm/Support/Compiler.h"
 // CHECK-NEXT:  #include <cstddef>
 // CHECK-NEXT:  #include <utility>
@@ -54,8 +55,6 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  namespace llvm {
 // CHECK-NEXT:  namespace tdl {
 // CHECK-EMPTY:
-// CHECK-NEXT:  struct VersionRange { int Min = 1; int Max = 0x7fffffff; };
-// CHECK-EMPTY:
 // CHECK-NEXT:  enum class Association {
 // CHECK-NEXT:    Block,
 // CHECK-NEXT:    Declaration,
@@ -102,14 +101,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  static constexpr std::size_t Clause_enumSize = 4;
 // CHECK-EMPTY:
 // CHECK-NEXT:  // Enumeration helper functions
-// CHECK-NEXT:  LLVM_ABI std::pair<Directive, VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Directive, directive::VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
 // CHECK-NEXT:  inline Directive getTdlDirectiveKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlDirectiveKindAndVersions(Str).first;
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ABI StringRef getTdlDirectiveName(Directive D, unsigned Ver = 0);
 // CHECK-EMPTY:
-// CHECK-NEXT:  LLVM_ABI std::pair<Clause, VersionRange> getTdlClauseKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Clause, directive::VersionRange> getTdlClauseKindAndVersions(StringRef Str);
 // CHECK-EMPTY:
 // CHECK-NEXT:  inline Clause getTdlClauseKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlClauseKindAndVersions(Str).first;
@@ -267,17 +266,18 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL:       #ifdef GEN_DIRECTIVES_IMPL
 // IMPL-NEXT:  #undef GEN_DIRECTIVES_IMPL
 // IMPL-EMPTY:
+// IMPL-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // IMPL-NEXT:  #include "llvm/Support/ErrorHandling.h"
 // IMPL-NEXT:  #include <utility>
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::tdl::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Directive, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::directive::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Directive, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("dira", {TDLD_dira, All})
 // IMPL-NEXT:     .Default({TDLD_dira, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLD_dira:
 // IMPL-NEXT:        return "dira";
@@ -285,9 +285,9 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Directive kind");
 // IMPL-NEXT:  }
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::tdl::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Clause, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::directive::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Clause, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("clausea", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clauseb", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clausec", {TDLC_clausec, All})
@@ -295,7 +295,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:     .Default({TDLC_clauseb, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLC_clausea:
 // IMPL-NEXT:        return "clausea";
diff --git a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
index 75f796abb7ce6..84438de067dce 100644
--- a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
@@ -77,6 +77,19 @@ static std::string getIdentifierName(const Record *Rec, StringRef Prefix) {
   return Prefix.str() + BaseRecord(Rec).getFormattedName();
 }
 
+using RecordWithSpelling = std::pair<const Record *, Spelling::Value>;
+
+static std::vector<RecordWithSpelling>
+getSpellings(ArrayRef<const Record *> Records) {
+  std::vector<RecordWithSpelling> List;
+  for (const Record *R : Records) {
+    Clause C(R);
+    llvm::transform(C.getSpellings(), std::back_inserter(List),
+                    [R](Spelling::Value V) { return std::make_pair(R, V); });
+  }
+  return List;
+}
+
 static void generateEnumExports(ArrayRef<const Record *> Records,
                                 raw_ostream &OS, StringRef Enum,
                                 StringRef Prefix) {
@@ -270,6 +283,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
     OS << "#include \"llvm/ADT/BitmaskEnum.h\"\n";
 
   OS << "#include \"llvm/ADT/StringRef.h\"\n";
+  OS << "#include \"llvm/Frontend/Directive/Spelling.h\"\n";
   OS << "#include \"llvm/Support/Compiler.h\"\n";
   OS << "#include <cstddef>\n"; // for size_t
   OS << "#include <utility>\n"; // for std::pair
@@ -285,13 +299,6 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   if (DirLang.hasEnableBitmaskEnumInNamespace())
     OS << "\nLLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();\n";
 
-#define AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED(x) #x
-#define AS_STRING(x) AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED(x)
-  OS << "\n";
-  OS << AS_STRING(STRUCT_VERSION_RANGE) << ";\n";
-#undef AS_STRING
-#undef AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED
-
   // Emit Directive associations
   std::vector<const Record *> Associations;
   copy_if(DirLang.getAssociations(), std::back_inserter(Associations),
@@ -324,7 +331,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   OS << "\n";
   OS << "// Enumeration helper functions\n";
 
-  OS << "LLVM_ABI std::pair<Directive, VersionRange> get" << Lang
+  OS << "LLVM_ABI std::pair<Directive, directive::VersionRange> get" << Lang
      << "DirectiveKindAndVersions(StringRef Str);\n";
 
   OS << "inline Directive get" << Lang << "DirectiveKind(StringRef Str) {\n";
@@ -336,7 +343,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
      << "DirectiveName(Directive D, unsigned Ver = 0);\n";
   OS << "\n";
 
-  OS << "LLVM_ABI std::pair<Clause, VersionRange> get" << Lang
+  OS << "LLVM_ABI std::pair<Clause, directive::VersionRange> get" << Lang
      << "ClauseKindAndVersions(StringRef Str);\n";
   OS << "\n";
 
@@ -373,6 +380,33 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   OS << "#endif // LLVM_" << Lang << "_INC\n";
 }
 
+// Given a list of spellings (for a given clause/directive), order them
+// in a way that allows the use of binary search to locate a spelling
+// for a specified version.
+static std::vector<Spelling::Value>
+orderSpellings(ArrayRef<Spelling::Value> Spellings) {
+  std::vector<Spelling::Value> List(Spellings.begin(), Spellings.end());
+
+  // There are two intertwined orderings: (1) the order between spellings
+  // (used here), and (2) the order between a spelling and a version (used
+  // at runtime).
+  // Define order (2) as such that the first A that is not less than V
+  // will be the selected spelling given V. Specifically,
+  //   V <(2) A  <=>  V < A.Min
+  //   A <(2) V  <=>  A.Max < V
+  //
+  // The orders have to be comp...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jun 5, 2025

@llvm/pr-subscribers-tablegen

Author: Krzysztof Parzyszek (kparzysz)

Changes

In "get<lang>DirectiveName(Kind, Version)", return the spelling that corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)" return the pair {Kind, VersionRange}, where VersionRange contains the minimum and the maximum versions that allow "Name" as a spelling. This applies to clauses as well. In general it applies to classes that have spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires a runtime search (which can fail in a general case). To avoid generating the search function inline, a small additional component of llvm/Frontent was added: LLVMFrontendDirective. The corresponding header file also defines C++ classes "Spelling" and "VersionRange", which are used in TableGen/DirectiveEmitter as well.

For background information see
https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507


Patch is 26.52 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143021.diff

10 Files Affected:

  • (added) llvm/include/llvm/Frontend/Directive/Spelling.h (+39)
  • (modified) llvm/include/llvm/TableGen/DirectiveEmitter.h (+8-17)
  • (modified) llvm/lib/Frontend/CMakeLists.txt (+1)
  • (added) llvm/lib/Frontend/Directive/CMakeLists.txt (+6)
  • (added) llvm/lib/Frontend/Directive/Spelling.cpp (+31)
  • (modified) llvm/lib/Frontend/OpenACC/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Frontend/OpenMP/CMakeLists.txt (+1)
  • (modified) llvm/test/TableGen/directive1.td (+20-14)
  • (modified) llvm/test/TableGen/directive2.td (+12-12)
  • (modified) llvm/utils/TableGen/Basic/DirectiveEmitter.cpp (+93-53)
diff --git a/llvm/include/llvm/Frontend/Directive/Spelling.h b/llvm/include/llvm/Frontend/Directive/Spelling.h
new file mode 100644
index 0000000000000..3ba0ae2296535
--- /dev/null
+++ b/llvm/include/llvm/Frontend/Directive/Spelling.h
@@ -0,0 +1,39 @@
+//===-- Spelling.h ------------------------------------------------ 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_FRONTEND_DIRECTIVE_SPELLING_H
+#define LLVM_FRONTEND_DIRECTIVE_SPELLING_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator_range.h"
+
+#include <limits>
+
+namespace llvm::directive {
+
+struct VersionRange {
+  static constexpr int MaxValue = std::numeric_limits<int>::max();
+  int Min = 1;
+  int Max = MaxValue;
+};
+
+inline bool operator<(const VersionRange &A, const VersionRange &B) {
+  if (A.Min != B.Min)
+    return A.Min < B.Min;
+  return A.Max < B.Max;
+}
+
+struct Spelling {
+  StringRef Name;
+  VersionRange Versions;
+};
+
+StringRef FindName(llvm::iterator_range<const Spelling *>, unsigned Version);
+
+} // namespace llvm::directive
+
+#endif // LLVM_FRONTEND_DIRECTIVE_SPELLING_H
diff --git a/llvm/include/llvm/TableGen/DirectiveEmitter.h b/llvm/include/llvm/TableGen/DirectiveEmitter.h
index dc2f75083ec0d..a2256716a7c8b 100644
--- a/llvm/include/llvm/TableGen/DirectiveEmitter.h
+++ b/llvm/include/llvm/TableGen/DirectiveEmitter.h
@@ -17,6 +17,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Frontend/Directive/Spelling.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/TableGen/Record.h"
 #include <algorithm>
@@ -113,29 +114,19 @@ class Versioned {
   constexpr static int IntWidth = 8 * sizeof(int);
 };
 
-// Range of specification versions: [Min, Max]
-// Default value: all possible versions.
-// This is the same structure as the one emitted into the generated sources.
-#define STRUCT_VERSION_RANGE                                                   \
-  struct VersionRange {                                                        \
-    int Min = 1;                                                               \
-    int Max = 0x7fffffff;                                                      \
-  }
-
-STRUCT_VERSION_RANGE;
-
 class Spelling : public Versioned {
 public:
-  using Value = std::pair<StringRef, VersionRange>;
+  using Value = llvm::directive::Spelling;
 
   Spelling(const Record *Def) : Def(Def) {}
 
   StringRef getText() const { return Def->getValueAsString("spelling"); }
-  VersionRange getVersions() const {
-    return VersionRange{getMinVersion(Def), getMaxVersion(Def)};
+  llvm::directive::VersionRange getVersions() const {
+    return llvm::directive::VersionRange{getMinVersion(Def),
+                                         getMaxVersion(Def)};
   }
 
-  Value get() const { return std::make_pair(getText(), getVersions()); }
+  Value get() const { return Value{getText(), getVersions()}; }
 
 private:
   const Record *Def;
@@ -177,9 +168,9 @@ class BaseRecord {
     // are added.
     Spelling::Value Oldest{"not found", {/*Min=*/INT_MAX, 0}};
     for (auto V : getSpellings())
-      if (V.second.Min < Oldest.second.Min)
+      if (V.Versions.Min < Oldest.Versions.Min)
         Oldest = V;
-    return Oldest.first;
+    return Oldest.Name;
   }
 
   // Returns the name of the directive formatted for output. Whitespace are
diff --git a/llvm/lib/Frontend/CMakeLists.txt b/llvm/lib/Frontend/CMakeLists.txt
index b305ce7d771ce..3b31e6f8dec96 100644
--- a/llvm/lib/Frontend/CMakeLists.txt
+++ b/llvm/lib/Frontend/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(Atomic)
+add_subdirectory(Directive)
 add_subdirectory(Driver)
 add_subdirectory(HLSL)
 add_subdirectory(OpenACC)
diff --git a/llvm/lib/Frontend/Directive/CMakeLists.txt b/llvm/lib/Frontend/Directive/CMakeLists.txt
new file mode 100644
index 0000000000000..a567e1affb171
--- /dev/null
+++ b/llvm/lib/Frontend/Directive/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_llvm_component_library(LLVMFrontendDirective
+  Spelling.cpp
+
+  LINK_COMPONENTS
+  Support
+)
diff --git a/llvm/lib/Frontend/Directive/Spelling.cpp b/llvm/lib/Frontend/Directive/Spelling.cpp
new file mode 100644
index 0000000000000..c808e625ee4fd
--- /dev/null
+++ b/llvm/lib/Frontend/Directive/Spelling.cpp
@@ -0,0 +1,31 @@
+//===-- Spelling.cpp ---------------------------------------------- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/Directive/Spelling.h"
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <cassert>
+
+llvm::StringRef llvm::directive::FindName(
+    llvm::iterator_range<const llvm::directive::Spelling *> Range,
+    unsigned Version) {
+  assert(llvm::isInt<8 * sizeof(int)>(Version) && "Version value out of range");
+
+  int V = Version;
+  Spelling Tmp{StringRef(), {V, V}};
+  auto F =
+      llvm::lower_bound(Range, Tmp, [](const Spelling &A, const Spelling &B) {
+        return A.Versions < B.Versions;
+      });
+  if (F != Range.end())
+    return F->Name;
+  return StringRef();
+}
diff --git a/llvm/lib/Frontend/OpenACC/CMakeLists.txt b/llvm/lib/Frontend/OpenACC/CMakeLists.txt
index f352014978690..4664b71407c48 100644
--- a/llvm/lib/Frontend/OpenACC/CMakeLists.txt
+++ b/llvm/lib/Frontend/OpenACC/CMakeLists.txt
@@ -9,5 +9,5 @@ add_llvm_component_library(LLVMFrontendOpenACC
   acc_gen
 )
 
-target_link_libraries(LLVMFrontendOpenACC LLVMSupport)
+target_link_libraries(LLVMFrontendOpenACC LLVMSupport LLVMFrontendDirective)
 
diff --git a/llvm/lib/Frontend/OpenMP/CMakeLists.txt b/llvm/lib/Frontend/OpenMP/CMakeLists.txt
index 35c607866a94e..5bf15ca3a8991 100644
--- a/llvm/lib/Frontend/OpenMP/CMakeLists.txt
+++ b/llvm/lib/Frontend/OpenMP/CMakeLists.txt
@@ -23,4 +23,5 @@ add_llvm_component_library(LLVMFrontendOpenMP
   BitReader
   FrontendOffloading
   FrontendAtomic
+  FrontendDirective
   )
diff --git a/llvm/test/TableGen/directive1.td b/llvm/test/TableGen/directive1.td
index 8196a30d03df4..1c8da26f50f4b 100644
--- a/llvm/test/TableGen/directive1.td
+++ b/llvm/test/TableGen/directive1.td
@@ -54,6 +54,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  #include "llvm/ADT/ArrayRef.h"
 // CHECK-NEXT:  #include "llvm/ADT/BitmaskEnum.h"
 // CHECK-NEXT:  #include "llvm/ADT/StringRef.h"
+// CHECK-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // CHECK-NEXT:  #include "llvm/Support/Compiler.h"
 // CHECK-NEXT:  #include <cstddef>
 // CHECK-NEXT:  #include <utility>
@@ -63,8 +64,6 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 // CHECK-EMPTY:
-// CHECK-NEXT:  struct VersionRange { int Min = 1; int Max = 0x7fffffff; };
-// CHECK-EMPTY:
 // CHECK-NEXT:  enum class Association {
 // CHECK-NEXT:    Block,
 // CHECK-NEXT:    Declaration,
@@ -126,14 +125,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  constexpr auto TDLCV_valc = AKind::TDLCV_valc;
 // CHECK-EMPTY:
 // CHECK-NEXT:  // Enumeration helper functions
-// CHECK-NEXT:  LLVM_ABI std::pair<Directive, VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Directive, directive::VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
 // CHECK-NEXT:  inline Directive getTdlDirectiveKind(StringRef Str) {
 // CHECK-NEXT:   return getTdlDirectiveKindAndVersions(Str).first;
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ABI StringRef getTdlDirectiveName(Directive D, unsigned Ver = 0);
 // CHECK-EMPTY:
-// CHECK-NEXT:  LLVM_ABI std::pair<Clause, VersionRange> getTdlClauseKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Clause, directive::VersionRange> getTdlClauseKindAndVersions(StringRef Str);
 // CHECK-EMPTY:
 // CHECK-NEXT:  inline Clause getTdlClauseKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlClauseKindAndVersions(Str).first;
@@ -320,17 +319,18 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL:       #ifdef GEN_DIRECTIVES_IMPL
 // IMPL-NEXT:  #undef GEN_DIRECTIVES_IMPL
 // IMPL-EMPTY:
+// IMPL-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // IMPL-NEXT:  #include "llvm/Support/ErrorHandling.h"
 // IMPL-NEXT:  #include <utility>
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::tdl::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Directive, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::directive::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Directive, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("dira", {TDLD_dira, All})
 // IMPL-NEXT:     .Default({TDLD_dira, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLD_dira:
 // IMPL-NEXT:        return "dira";
@@ -338,23 +338,29 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Directive kind");
 // IMPL-NEXT:  }
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::tdl::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Clause, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::directive::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Clause, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("clausea", {TDLC_clausea, All})
 // IMPL-NEXT:     .Case("clauseb", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clausec", {TDLC_clausec, All})
+// IMPL-NEXT:     .Case("ccccccc", {TDLC_clausec, All})
 // IMPL-NEXT:     .Default({TDLC_clauseb, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLC_clausea:
 // IMPL-NEXT:        return "clausea";
 // IMPL-NEXT:      case TDLC_clauseb:
 // IMPL-NEXT:        return "clauseb";
-// IMPL-NEXT:      case TDLC_clausec:
-// IMPL-NEXT:        return "clausec";
+// IMPL-NEXT:      case TDLC_clausec: {
+// IMPL-NEXT:        static const llvm::directive::Spelling TDLC_clausec_spellings[] = {
+// IMPL-NEXT:            {"clausec", {1, 2147483647}},
+// IMPL-NEXT:            {"ccccccc", {1, 2147483647}},
+// IMPL-NEXT:        };
+// IMPL-NEXT:        return llvm::directive::FindName(TDLC_clausec_spellings, Version);
+// IMPL-NEXT:      }
 // IMPL-NEXT:    }
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Clause kind");
 // IMPL-NEXT:  }
diff --git a/llvm/test/TableGen/directive2.td b/llvm/test/TableGen/directive2.td
index ead6aa2637b76..3a64bb3900a31 100644
--- a/llvm/test/TableGen/directive2.td
+++ b/llvm/test/TableGen/directive2.td
@@ -47,6 +47,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-EMPTY:
 // CHECK-NEXT:  #include "llvm/ADT/ArrayRef.h"
 // CHECK-NEXT:  #include "llvm/ADT/StringRef.h"
+// CHECK-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // CHECK-NEXT:  #include "llvm/Support/Compiler.h"
 // CHECK-NEXT:  #include <cstddef>
 // CHECK-NEXT:  #include <utility>
@@ -54,8 +55,6 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  namespace llvm {
 // CHECK-NEXT:  namespace tdl {
 // CHECK-EMPTY:
-// CHECK-NEXT:  struct VersionRange { int Min = 1; int Max = 0x7fffffff; };
-// CHECK-EMPTY:
 // CHECK-NEXT:  enum class Association {
 // CHECK-NEXT:    Block,
 // CHECK-NEXT:    Declaration,
@@ -102,14 +101,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // CHECK-NEXT:  static constexpr std::size_t Clause_enumSize = 4;
 // CHECK-EMPTY:
 // CHECK-NEXT:  // Enumeration helper functions
-// CHECK-NEXT:  LLVM_ABI std::pair<Directive, VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Directive, directive::VersionRange> getTdlDirectiveKindAndVersions(StringRef Str);
 // CHECK-NEXT:  inline Directive getTdlDirectiveKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlDirectiveKindAndVersions(Str).first;
 // CHECK-NEXT:  }
 // CHECK-EMPTY:
 // CHECK-NEXT:  LLVM_ABI StringRef getTdlDirectiveName(Directive D, unsigned Ver = 0);
 // CHECK-EMPTY:
-// CHECK-NEXT:  LLVM_ABI std::pair<Clause, VersionRange> getTdlClauseKindAndVersions(StringRef Str);
+// CHECK-NEXT:  LLVM_ABI std::pair<Clause, directive::VersionRange> getTdlClauseKindAndVersions(StringRef Str);
 // CHECK-EMPTY:
 // CHECK-NEXT:  inline Clause getTdlClauseKind(StringRef Str) {
 // CHECK-NEXT:    return getTdlClauseKindAndVersions(Str).first;
@@ -267,17 +266,18 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL:       #ifdef GEN_DIRECTIVES_IMPL
 // IMPL-NEXT:  #undef GEN_DIRECTIVES_IMPL
 // IMPL-EMPTY:
+// IMPL-NEXT:  #include "llvm/Frontend/Directive/Spelling.h"
 // IMPL-NEXT:  #include "llvm/Support/ErrorHandling.h"
 // IMPL-NEXT:  #include <utility>
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::tdl::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Directive, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Directive, llvm::directive::VersionRange> llvm::tdl::getTdlDirectiveKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Directive, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("dira", {TDLD_dira, All})
 // IMPL-NEXT:     .Default({TDLD_dira, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlDirectiveName(llvm::tdl::Directive Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLD_dira:
 // IMPL-NEXT:        return "dira";
@@ -285,9 +285,9 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:    llvm_unreachable("Invalid Tdl Directive kind");
 // IMPL-NEXT:  }
 // IMPL-EMPTY:
-// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::tdl::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
-// IMPL-NEXT:   VersionRange All{}; // Default-initialized to "all-versions"
-// IMPL-NEXT:   return StringSwitch<std::pair<Clause, VersionRange>>(Str)
+// IMPL-NEXT: std::pair<llvm::tdl::Clause, llvm::directive::VersionRange> llvm::tdl::getTdlClauseKindAndVersions(llvm::StringRef Str) {
+// IMPL-NEXT:   directive::VersionRange All; // Default-initialized to "all versions"
+// IMPL-NEXT:   return StringSwitch<std::pair<Clause, directive::VersionRange>>(Str)
 // IMPL-NEXT:     .Case("clausea", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clauseb", {TDLC_clauseb, All})
 // IMPL-NEXT:     .Case("clausec", {TDLC_clausec, All})
@@ -295,7 +295,7 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
 // IMPL-NEXT:     .Default({TDLC_clauseb, All});
 // IMPL-NEXT: }
 // IMPL-EMPTY:
-// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned) {
+// IMPL-NEXT:  llvm::StringRef llvm::tdl::getTdlClauseName(llvm::tdl::Clause Kind, unsigned Version) {
 // IMPL-NEXT:    switch (Kind) {
 // IMPL-NEXT:      case TDLC_clausea:
 // IMPL-NEXT:        return "clausea";
diff --git a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
index 75f796abb7ce6..84438de067dce 100644
--- a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
@@ -77,6 +77,19 @@ static std::string getIdentifierName(const Record *Rec, StringRef Prefix) {
   return Prefix.str() + BaseRecord(Rec).getFormattedName();
 }
 
+using RecordWithSpelling = std::pair<const Record *, Spelling::Value>;
+
+static std::vector<RecordWithSpelling>
+getSpellings(ArrayRef<const Record *> Records) {
+  std::vector<RecordWithSpelling> List;
+  for (const Record *R : Records) {
+    Clause C(R);
+    llvm::transform(C.getSpellings(), std::back_inserter(List),
+                    [R](Spelling::Value V) { return std::make_pair(R, V); });
+  }
+  return List;
+}
+
 static void generateEnumExports(ArrayRef<const Record *> Records,
                                 raw_ostream &OS, StringRef Enum,
                                 StringRef Prefix) {
@@ -270,6 +283,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
     OS << "#include \"llvm/ADT/BitmaskEnum.h\"\n";
 
   OS << "#include \"llvm/ADT/StringRef.h\"\n";
+  OS << "#include \"llvm/Frontend/Directive/Spelling.h\"\n";
   OS << "#include \"llvm/Support/Compiler.h\"\n";
   OS << "#include <cstddef>\n"; // for size_t
   OS << "#include <utility>\n"; // for std::pair
@@ -285,13 +299,6 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   if (DirLang.hasEnableBitmaskEnumInNamespace())
     OS << "\nLLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();\n";
 
-#define AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED(x) #x
-#define AS_STRING(x) AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED(x)
-  OS << "\n";
-  OS << AS_STRING(STRUCT_VERSION_RANGE) << ";\n";
-#undef AS_STRING
-#undef AS_STRING_HELPER_TO_GET_THE_ARGUMENT_MACRO_EXPANDED
-
   // Emit Directive associations
   std::vector<const Record *> Associations;
   copy_if(DirLang.getAssociations(), std::back_inserter(Associations),
@@ -324,7 +331,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   OS << "\n";
   OS << "// Enumeration helper functions\n";
 
-  OS << "LLVM_ABI std::pair<Directive, VersionRange> get" << Lang
+  OS << "LLVM_ABI std::pair<Directive, directive::VersionRange> get" << Lang
      << "DirectiveKindAndVersions(StringRef Str);\n";
 
   OS << "inline Directive get" << Lang << "DirectiveKind(StringRef Str) {\n";
@@ -336,7 +343,7 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
      << "DirectiveName(Directive D, unsigned Ver = 0);\n";
   OS << "\n";
 
-  OS << "LLVM_ABI std::pair<Clause, VersionRange> get" << Lang
+  OS << "LLVM_ABI std::pair<Clause, directive::VersionRange> get" << Lang
      << "ClauseKindAndVersions(StringRef Str);\n";
   OS << "\n";
 
@@ -373,6 +380,33 @@ static void emitDirectivesDecl(const RecordKeeper &Records, raw_ostream &OS) {
   OS << "#endif // LLVM_" << Lang << "_INC\n";
 }
 
+// Given a list of spellings (for a given clause/directive), order them
+// in a way that allows the use of binary search to locate a spelling
+// for a specified version.
+static std::vector<Spelling::Value>
+orderSpellings(ArrayRef<Spelling::Value> Spellings) {
+  std::vector<Spelling::Value> List(Spellings.begin(), Spellings.end());
+
+  // There are two intertwined orderings: (1) the order between spellings
+  // (used here), and (2) the order between a spelling and a version (used
+  // at runtime).
+  // Define order (2) as such that the first A that is not less than V
+  // will be the selected spelling given V. Specifically,
+  //   V <(2) A  <=>  V < A.Min
+  //   A <(2) V  <=>  A.Max < V
+  //
+  // The orders have to be comp...
[truncated]

@jurahul
Copy link
Contributor

jurahul commented Jun 6, 2025

LGTM, just some minor comments.

kparzysz added 3 commits June 6, 2025 08:35
The default value of the Version parameter in get<Lang><Enum>Name()
was 0, which was not in the range [1, 0x7fffffff]. To fix this, it
was either to change the default value to 1, or to lower the minimum
version to 0. The latter seemed like a better choice, since 0 is a
natural choice for a lower bound on version numbers.
@kparzysz
Copy link
Contributor Author

kparzysz commented Jun 6, 2025

Running local checks before pushing updates...

Copy link

github-actions bot commented Jun 6, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@kparzysz kparzysz merged commit 7b2aa02 into main Jun 6, 2025
7 checks passed
@kparzysz kparzysz deleted the users/kparzysz/spr/t08-versioned-spellings branch June 6, 2025 22:07
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jun 7, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-darwin running on doug-worker-3 while building llvm at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/23/builds/11030

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clangd Unit Tests :: ./ClangdTests/0/11' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/tools/extra/clangd/unittests/./ClangdTests-Clangd Unit Tests-80671-0-11.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=11 GTEST_SHARD_INDEX=0 /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/tools/extra/clangd/unittests/./ClangdTests
--

Script:
--
/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/clang/tools/extra/clangd/unittests/./ClangdTests --gtest_filter=BackgroundIndexTest.Config
--
Enqueueing 1 commands for indexing
Failed to load shard: /clangd-test/foo.cpp
Enqueueing 1 commands for indexing
Enqueueing 1 commands for indexing
Failed to load shard: /clangd-test/bar.cpp
/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp:159: Failure
Value of: Idx.blockUntilIdleForTest()
  Actual: false
Expected: true

Indexing foo.cpp (digest:=8709C880CCEBA03C)
Indexing bar.cpp (digest:=2CD19DD50C281BBA)
Indexed foo.cpp (2 symbols, 1 refs, 2 files)
Indexed bar.cpp (2 symbols, 1 refs, 2 files)

/Users/buildbot/buildbot-root/x86_64-darwin/llvm-project/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp:159
Value of: Idx.blockUntilIdleForTest()
  Actual: false
Expected: true



********************


rorth pushed a commit to rorth/llvm-project that referenced this pull request Jun 11, 2025
…#143021)

In "get<lang>DirectiveName(Kind, Version)", return the spelling that
corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)"
return the pair {Kind, VersionRange}, where VersionRange contains the
minimum and the maximum versions that allow "Name" as a spelling. This
applies to clauses as well. In general it applies to classes that have
spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires
a runtime search (which can fail in a general case). To avoid generating
the search function inline, a small additional component of
llvm/Frontent was added: LLVMFrontendDirective. The corresponding header
file also defines C++ classes "Spelling" and "VersionRange", which are
used in TableGen/DirectiveEmitter as well.

For background information see

https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507
DhruvSrivastavaX pushed a commit to DhruvSrivastavaX/lldb-for-aix that referenced this pull request Jun 12, 2025
…#143021)

In "get<lang>DirectiveName(Kind, Version)", return the spelling that
corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)"
return the pair {Kind, VersionRange}, where VersionRange contains the
minimum and the maximum versions that allow "Name" as a spelling. This
applies to clauses as well. In general it applies to classes that have
spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires
a runtime search (which can fail in a general case). To avoid generating
the search function inline, a small additional component of
llvm/Frontent was added: LLVMFrontendDirective. The corresponding header
file also defines C++ classes "Spelling" and "VersionRange", which are
used in TableGen/DirectiveEmitter as well.

For background information see

https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507
tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
…#143021)

In "get<lang>DirectiveName(Kind, Version)", return the spelling that
corresponds to Version, and in "get<lang>DirectiveKindAndVersions(Name)"
return the pair {Kind, VersionRange}, where VersionRange contains the
minimum and the maximum versions that allow "Name" as a spelling. This
applies to clauses as well. In general it applies to classes that have
spellings (defined via TableGen class "Spelling").

Given a Kind and a Version, getting the corresponding spelling requires
a runtime search (which can fail in a general case). To avoid generating
the search function inline, a small additional component of
llvm/Frontent was added: LLVMFrontendDirective. The corresponding header
file also defines C++ classes "Spelling" and "VersionRange", which are
used in TableGen/DirectiveEmitter as well.

For background information see

https://discourse.llvm.org/t/rfc-alternative-spellings-of-openmp-directives/85507
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants