Skip to content

[TableGen] Add !instances operator to get defined records #129680

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 5 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions llvm/docs/TableGen/ProgRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 different
syntax compared to other bang operators, so it is defined separately:
Expand Down Expand Up @@ -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.
Expand Down
39 changes: 39 additions & 0 deletions llvm/include/llvm/TableGen/Record.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ class Init {
IK_FoldOpInit,
IK_IsAOpInit,
IK_ExistsOpInit,
IK_InstancesOpInit,
IK_AnonymousNameInit,
IK_StringInit,
IK_VarInit,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
65 changes: 65 additions & 0 deletions llvm/lib/TableGen/Record.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/TableGen/TGLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/TableGen/TGLexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ enum TokKind {
XSize,
XEmpty,
XInitialized,
XInstances,
XIf,
XCond,
XEq,
Expand Down
43 changes: 43 additions & 0 deletions llvm/lib/TableGen/TGParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
133 changes: 133 additions & 0 deletions llvm/test/TableGen/instances.td
Original file line number Diff line number Diff line change
@@ -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
Loading