[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