[llvm] 8836128 - [TableGen] Add `!instances` operator to get defined records (#129680)

via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 28 01:31:04 PDT 2025


Author: Pengcheng Wang
Date: 2025-03-28T16:31:00+08:00
New Revision: 883612859bd255fc964c121ea6d3b1a9fb37fc65

URL: https://github.com/llvm/llvm-project/commit/883612859bd255fc964c121ea6d3b1a9fb37fc65
DIFF: https://github.com/llvm/llvm-project/commit/883612859bd255fc964c121ea6d3b1a9fb37fc65.diff

LOG: [TableGen] Add `!instances` operator to get defined records (#129680)

The format is: `!instances<T>([regex])`.
    
This operator produces a list of records whose type is `T`. If
`regex` is provided, only records whose name matches the regular
expression `regex` will be included. The format of `regex` is ERE
(Extended POSIX Regular Expressions).

Added: 
    llvm/test/TableGen/instances.td

Modified: 
    llvm/docs/TableGen/ProgRef.rst
    llvm/include/llvm/TableGen/Record.h
    llvm/lib/TableGen/Record.cpp
    llvm/lib/TableGen/TGLexer.cpp
    llvm/lib/TableGen/TGLexer.h
    llvm/lib/TableGen/TGParser.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst
index 0983c6283f7e2..fe0564d2990df 100644
--- a/llvm/docs/TableGen/ProgRef.rst
+++ b/llvm/docs/TableGen/ProgRef.rst
@@ -223,13 +223,13 @@ TableGen provides "bang operators" that have a wide variety of uses:
                : !div         !empty       !eq          !exists      !filter
                : !find        !foldl       !foreach     !ge          !getdagarg
                : !getdagname  !getdagop    !gt          !head        !if
-               : !initialized !interleave  !isa         !le          !listconcat
-               : !listflatten !listremove  !listsplat   !logtwo      !lt
-               : !match       !mul         !ne          !not         !or
-               : !range       !repr        !setdagarg   !setdagname  !setdagop
-               : !shl         !size        !sra         !srl         !strconcat
-               : !sub         !subst       !substr      !tail        !tolower
-               : !toupper     !xor
+               : !initialized !instances   !interleave  !isa         !le
+               : !listconcat  !listflatten !listremove  !listsplat   !logtwo
+               : !lt          !match       !mul         !ne          !not
+               : !or          !range       !repr        !setdagarg   !setdagname
+               : !setdagop    !shl         !size        !sra         !srl
+               : !strconcat   !sub         !subst       !substr      !tail
+               : !tolower     !toupper     !xor
 
 The ``!cond`` operator has a slightly 
diff erent
 syntax compared to other bang operators, so it is defined separately:
@@ -1836,6 +1836,15 @@ and non-0 as true.
   This operator produces 1 if *a* is not the uninitialized value (``?``) and 0
   otherwise.
 
+``!instances<``\ *type*\ ``>([``\ *regex*\ ``])``
+    This operator produces a list of records whose type is *type*. If *regex*
+    is provided, only records whose name matches the regular expression *regex*
+    will be included. The format of *regex* is ERE (Extended POSIX Regular
+    Expressions).
+
+    If ``!instances`` is in a class/multiclass/foreach, only these records of
+    *type* that have been instantiated will be considered.
+
 ``!interleave(``\ *list*\ ``,`` *delim*\ ``)``
     This operator concatenates the items in the *list*, interleaving the
     *delim* string between each pair, and produces the resulting string.

diff  --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h
index ae505631b5433..d3771a3731673 100644
--- a/llvm/include/llvm/TableGen/Record.h
+++ b/llvm/include/llvm/TableGen/Record.h
@@ -316,6 +316,7 @@ class Init {
     IK_FoldOpInit,
     IK_IsAOpInit,
     IK_ExistsOpInit,
+    IK_InstancesOpInit,
     IK_AnonymousNameInit,
     IK_StringInit,
     IK_VarInit,
@@ -1192,6 +1193,41 @@ class ExistsOpInit final : public TypedInit, public FoldingSetNode {
   std::string getAsString() const override;
 };
 
+/// !instances<type>([regex]) - Produces a list of records whose type is `type`.
+/// If `regex` is provided, only records whose name matches the regular
+/// expression `regex` will be included.
+class InstancesOpInit final : public TypedInit, public FoldingSetNode {
+private:
+  const RecTy *Type;
+  const Init *Regex;
+
+  InstancesOpInit(const RecTy *Type, const Init *Regex)
+      : TypedInit(IK_InstancesOpInit, ListRecTy::get(Type)), Type(Type),
+        Regex(Regex) {}
+
+public:
+  InstancesOpInit(const InstancesOpInit &) = delete;
+  InstancesOpInit &operator=(const InstancesOpInit &) = delete;
+
+  static bool classof(const Init *I) {
+    return I->getKind() == IK_InstancesOpInit;
+  }
+
+  static const InstancesOpInit *get(const RecTy *Type, const Init *Regex);
+
+  void Profile(FoldingSetNodeID &ID) const;
+
+  const Init *Fold(const Record *CurRec, bool IsFinal = false) const;
+
+  bool isComplete() const override { return false; }
+
+  const Init *resolveReferences(Resolver &R) const override;
+
+  const Init *getBit(unsigned Bit) const override;
+
+  std::string getAsString() const override;
+};
+
 /// 'Opcode' - Represent a reference to an entire variable object.
 class VarInit final : public TypedInit {
   const Init *VarName;
@@ -1982,6 +2018,9 @@ class RecordKeeper {
     bool Ins = Defs.try_emplace(std::string(R->getName()), std::move(R)).second;
     (void)Ins;
     assert(Ins && "Record already exists");
+    // Clear cache
+    if (!Cache.empty())
+      Cache.clear();
   }
 
   void addExtraGlobal(StringRef Name, const Init *I) {

diff  --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index c5b9b670b6f42..d182b647aa931 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -84,6 +84,7 @@ struct RecordKeeperImpl {
   FoldingSet<FoldOpInit> TheFoldOpInitPool;
   FoldingSet<IsAOpInit> TheIsAOpInitPool;
   FoldingSet<ExistsOpInit> TheExistsOpInitPool;
+  FoldingSet<InstancesOpInit> TheInstancesOpInitPool;
   DenseMap<std::pair<const RecTy *, const Init *>, VarInit *> TheVarInitPool;
   DenseMap<std::pair<const TypedInit *, unsigned>, VarBitInit *>
       TheVarBitInitPool;
@@ -2222,6 +2223,70 @@ std::string ExistsOpInit::getAsString() const {
       .str();
 }
 
+static void ProfileInstancesOpInit(FoldingSetNodeID &ID, const RecTy *Type,
+                                   const Init *Regex) {
+  ID.AddPointer(Type);
+  ID.AddPointer(Regex);
+}
+
+const InstancesOpInit *InstancesOpInit::get(const RecTy *Type,
+                                            const Init *Regex) {
+  FoldingSetNodeID ID;
+  ProfileInstancesOpInit(ID, Type, Regex);
+
+  detail::RecordKeeperImpl &RK = Regex->getRecordKeeper().getImpl();
+  void *IP = nullptr;
+  if (const InstancesOpInit *I =
+          RK.TheInstancesOpInitPool.FindNodeOrInsertPos(ID, IP))
+    return I;
+
+  InstancesOpInit *I = new (RK.Allocator) InstancesOpInit(Type, Regex);
+  RK.TheInstancesOpInitPool.InsertNode(I, IP);
+  return I;
+}
+
+void InstancesOpInit::Profile(FoldingSetNodeID &ID) const {
+  ProfileInstancesOpInit(ID, Type, Regex);
+}
+
+const Init *InstancesOpInit::Fold(const Record *CurRec, bool IsFinal) const {
+  if (CurRec && !IsFinal)
+    return this;
+
+  const auto *RegexInit = dyn_cast<StringInit>(Regex);
+  if (!RegexInit)
+    return this;
+
+  StringRef RegexStr = RegexInit->getValue();
+  llvm::Regex Matcher(RegexStr);
+  if (!Matcher.isValid())
+    PrintFatalError(Twine("invalid regex '") + RegexStr + Twine("'"));
+
+  const RecordKeeper &RK = Type->getRecordKeeper();
+  SmallVector<Init *, 8> Selected;
+  for (auto &Def : RK.getAllDerivedDefinitionsIfDefined(Type->getAsString()))
+    if (Matcher.match(Def->getName()))
+      Selected.push_back(Def->getDefInit());
+
+  return ListInit::get(Selected, Type);
+}
+
+const Init *InstancesOpInit::resolveReferences(Resolver &R) const {
+  const Init *NewRegex = Regex->resolveReferences(R);
+  if (Regex != NewRegex || R.isFinal())
+    return get(Type, NewRegex)->Fold(R.getCurrentRecord(), R.isFinal());
+  return this;
+}
+
+const Init *InstancesOpInit::getBit(unsigned Bit) const {
+  return VarBitInit::get(this, Bit);
+}
+
+std::string InstancesOpInit::getAsString() const {
+  return "!instances<" + Type->getAsString() + ">(" + Regex->getAsString() +
+         ")";
+}
+
 const RecTy *TypedInit::getFieldType(const StringInit *FieldName) const {
   if (const auto *RecordType = dyn_cast<RecordRecTy>(getType())) {
     for (const Record *Rec : RecordType->getClasses()) {

diff  --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp
index 0b2f927446b1e..4da2505cc065a 100644
--- a/llvm/lib/TableGen/TGLexer.cpp
+++ b/llvm/lib/TableGen/TGLexer.cpp
@@ -632,6 +632,7 @@ tgtok::TokKind TGLexer::LexExclaim() {
           .Case("strconcat", tgtok::XStrConcat)
           .Case("initialized", tgtok::XInitialized)
           .Case("interleave", tgtok::XInterleave)
+          .Case("instances", tgtok::XInstances)
           .Case("substr", tgtok::XSubstr)
           .Case("find", tgtok::XFind)
           .Cases("setdagop", "setop", tgtok::XSetDagOp) // !setop is deprecated.

diff  --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h
index ef9205197decf..a5b10b37e9886 100644
--- a/llvm/lib/TableGen/TGLexer.h
+++ b/llvm/lib/TableGen/TGLexer.h
@@ -137,6 +137,7 @@ enum TokKind {
   XSize,
   XEmpty,
   XInitialized,
+  XInstances,
   XIf,
   XCond,
   XEq,

diff  --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index 787c3e64beac2..3d914b94ff62f 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -1455,6 +1455,49 @@ const Init *TGParser::ParseOperation(Record *CurRec, const RecTy *ItemType) {
     return (ExistsOpInit::get(Type, Expr))->Fold(CurRec);
   }
 
+  case tgtok::XInstances: {
+    // Value ::= !instances '<' Type '>' '(' Regex? ')'
+    Lex.Lex(); // eat the operation.
+
+    const RecTy *Type = ParseOperatorType();
+    if (!Type)
+      return nullptr;
+
+    if (!consume(tgtok::l_paren)) {
+      TokError("expected '(' after type of !instances");
+      return nullptr;
+    }
+
+    // The Regex can be optional.
+    const Init *Regex;
+    if (Lex.getCode() != tgtok::r_paren) {
+      SMLoc RegexLoc = Lex.getLoc();
+      Regex = ParseValue(CurRec);
+
+      const auto *RegexType = dyn_cast<TypedInit>(Regex);
+      if (!RegexType) {
+        Error(RegexLoc, "expected string type argument in !instances operator");
+        return nullptr;
+      }
+
+      const auto *SType = dyn_cast<StringRecTy>(RegexType->getType());
+      if (!SType) {
+        Error(RegexLoc, "expected string type argument in !instances operator");
+        return nullptr;
+      }
+    } else {
+      // Use wildcard when Regex is not specified.
+      Regex = StringInit::get(Records, ".*");
+    }
+
+    if (!consume(tgtok::r_paren)) {
+      TokError("expected ')' in !instances");
+      return nullptr;
+    }
+
+    return InstancesOpInit::get(Type, Regex)->Fold(CurRec);
+  }
+
   case tgtok::XConcat:
   case tgtok::XMatch:
   case tgtok::XADD:

diff  --git a/llvm/test/TableGen/instances.td b/llvm/test/TableGen/instances.td
new file mode 100644
index 0000000000000..a49b2de5f7520
--- /dev/null
+++ b/llvm/test/TableGen/instances.td
@@ -0,0 +1,133 @@
+// RUN: llvm-tblgen %s | FileCheck %s
+// RUN: not llvm-tblgen -DERROR1 %s 2>&1 | FileCheck --check-prefix=ERROR1 %s
+// RUN: not llvm-tblgen -DERROR2 %s 2>&1 | FileCheck --check-prefix=ERROR2 %s
+// RUN: not llvm-tblgen -DERROR3 %s 2>&1 | FileCheck --check-prefix=ERROR3 %s
+// XFAIL: vg_leak
+
+class A;
+def a0 : A;
+def a1 : A;
+
+class B : A;
+def b0 : B;
+def b1 : B;
+
+// CHECK-LABEL: def test0_instances_A {
+// CHECK-NEXT:    list<A> instances = [a0, a1, b0, b1];
+// CHECK-NEXT:  }
+def test0_instances_A {
+  list<A> instances = !instances<A>();
+}
+
+// CHECK-LABEL: def test1_instances_A_x0 {
+// CHECK-NEXT:    list<A> instances = [a0, b0];
+// CHECK-NEXT:  }
+def test1_instances_A_x0 {
+  list<A> instances = !instances<A>(".*0");
+}
+
+// CHECK-LABEL: def test2_instances_A_x1 {
+// CHECK-NEXT:    list<A> instances = [a1, b1];
+// CHECK-NEXT:  }
+def test2_instances_A_x1 {
+  list<A> instances = !instances<A>(".*1");
+}
+
+// CHECK-LABEL: def test3_instances_B {
+// CHECK-NEXT:    list<B> instances = [b0, b1];
+// CHECK-NEXT:  }
+def test3_instances_B {
+  list<B> instances = !instances<B>();
+}
+
+//-----------------------------------------------------------------------------//
+
+def a2 : A;
+def b2 : B;
+
+class ClassTest {
+   list<A> instances_A = !instances<A>();
+   list<B> instances_B = !instances<B>();
+}
+
+def a3 : A;
+def b3 : B;
+
+def test4_in_class_def : ClassTest;
+// CHECK-LABEL: def test4_in_class_def {
+// CHECK-NEXT:    list<A> instances_A = [a0, a1, a2, a3, b0, b1, b2, b3];
+// CHECK-NEXT:    list<B> instances_B = [b0, b1, b2, b3];
+// CHECK-NEXT:  }
+
+//-----------------------------------------------------------------------------//
+// Self-recurrence is not supported, so it won't be count in.
+
+// CHECK-LABEL: def test5_self_recurrence {
+// CHECK-NEXT:    list<A> instances_A = [a0, a1, a2, a3, b0, b1, b2, b3];
+// CHECK-NEXT:  }
+def test5_self_recurrence : A {
+   list<A> instances_A = !instances<A>();
+}
+
+//-----------------------------------------------------------------------------//
+// Test these in multiclasses/loops.
+
+class C {
+  list<C> instances_C = !instances<C>();
+}
+
+multiclass MultiClassTest {
+  foreach i = 0-2 in {
+    def "c"#i : C;
+  }
+}
+
+// CHECK-LABEL: def test6_in_multiclass_def_c0 {
+// CHECK-NEXT:    list<C> instances_C = [];
+// CHECK-NEXT:  }
+// CHECK-LABEL: def test6_in_multiclass_def_c1 {
+// CHECK-NEXT:    list<C> instances_C = [test6_in_multiclass_def_c0];
+// CHECK-NEXT:  }
+// CHECK-LABEL: def test6_in_multiclass_def_c2 {
+// CHECK-NEXT:    list<C> instances_C = [test6_in_multiclass_def_c0, test6_in_multiclass_def_c1];
+// CHECK-NEXT:  }
+defm test6_in_multiclass_def_ : MultiClassTest;
+
+//-----------------------------------------------------------------------------//
+// Default argument/temporary actual parameter will be considered as well.
+class D<int n>;
+
+class TestArgument<D d = D<0>> {
+  list<D> instances_D = !instances<D>();
+}
+
+// CHECK-LABEL: def test7_default_arg {
+// CHECK-NEXT:    list<D> instances_D = [anonymous_0];
+// CHECK-NEXT:  }
+def test7_default_arg : TestArgument;
+
+// CHECK-LABEL: def test8_anonymous0_arg {
+// CHECK-NEXT:    list<D> instances_D = [anonymous_0, anonymous_1];
+// CHECK-NEXT:  }
+// CHECK-LABEL: def test8_anonymous1_arg {
+// CHECK-NEXT:    list<D> instances_D = [anonymous_0, anonymous_1, anonymous_2];
+// CHECK-NEXT:  }
+def test8_anonymous0_arg : TestArgument<D<1>>;
+def test8_anonymous1_arg : TestArgument<D<2>>;
+
+//-----------------------------------------------------------------------------//
+
+#ifdef ERROR1
+defvar error1 = !instances<A>(123);
+// ERROR1: error: expected string type argument in !instances operator
+#endif
+
+#ifdef ERROR2
+defvar error2 = !instances<1>("");
+// ERROR2: error: Unknown token when expecting a type
+#endif
+
+#ifdef ERROR3
+defvar error3 = !instances<A>("([)]");
+// ERROR3: error: invalid regex '([)]'
+#endif


        


More information about the llvm-commits mailing list