[llvm] 6344848 - [TableGen] Add new operator !exists

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 22 20:13:13 PDT 2022


Author: wangpc
Date: 2022-06-23T11:11:47+08:00
New Revision: 634484885ccf89de0c3f296ac8380b26f4c6763d

URL: https://github.com/llvm/llvm-project/commit/634484885ccf89de0c3f296ac8380b26f4c6763d
DIFF: https://github.com/llvm/llvm-project/commit/634484885ccf89de0c3f296ac8380b26f4c6763d.diff

LOG: [TableGen] Add new operator !exists

We can cast a string to a record via !cast, but we have no mechanism
to check if it is valid and TableGen will raise an error if failed to
cast. Besides, we have no semantic null in TableGen (we have `?` but
different backends handle uninitialized value differently), so operator
like `dyn_cast<>` is hard to implement.

In this patch, we add a new operator `!exists<T>(s)` to check whether
a record with type `T` and name `s` exists. Self-references are allowed
just like `!cast`.

By doing these, we can write code like:
```
class dyn_cast_to_record<string name> {
  R value = !if(!exists<R>(name), !cast<R>(name), default_value);
}
defvar v = dyn_cast_to_record<"R0">.value; // R0 or default_value.
```

Reviewed By: tra, nhaehnle

Differential Revision: https://reviews.llvm.org/D127948

Added: 
    llvm/test/TableGen/exists-error-non-string.td
    llvm/test/TableGen/exists-error-record.td
    llvm/test/TableGen/exists-error-uninitialized.td
    llvm/test/TableGen/exists.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 4d02ceb98cdde..0bafc92ae8953 100644
--- a/llvm/docs/TableGen/ProgRef.rst
+++ b/llvm/docs/TableGen/ProgRef.rst
@@ -1724,6 +1724,10 @@ and non-0 as true.
     This operator produces 1 if the type of *a* is a subtype of the given *type*; 0
     otherwise.
 
+``!exists<``\ *type*\ ``>(``\ *name*\ ``)``
+    This operator produces 1 if a record of the given *type* whose name is *name*
+    exists; 0 otherwise. *name* should be of type *string*.
+
 ``!le(``\ *a*\ ``,`` *b*\ ``)``
     This operator produces 1 if *a* is less than or equal to *b*; 0 otherwise.
     The arguments must be ``bit``, ``bits``, ``int``, or ``string`` values.

diff  --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h
index 8c56354528cd1..44daad976c128 100644
--- a/llvm/include/llvm/TableGen/Record.h
+++ b/llvm/include/llvm/TableGen/Record.h
@@ -311,6 +311,7 @@ class Init {
     IK_CondOpInit,
     IK_FoldOpInit,
     IK_IsAOpInit,
+    IK_ExistsOpInit,
     IK_AnonymousNameInit,
     IK_StringInit,
     IK_VarInit,
@@ -1095,6 +1096,40 @@ class IsAOpInit : public TypedInit, public FoldingSetNode {
   std::string getAsString() const override;
 };
 
+/// !exists<type>(expr) - Dynamically determine if a record of `type` named
+/// `expr` exists.
+class ExistsOpInit : public TypedInit, public FoldingSetNode {
+private:
+  RecTy *CheckType;
+  Init *Expr;
+
+  ExistsOpInit(RecTy *CheckType, Init *Expr)
+      : TypedInit(IK_ExistsOpInit, IntRecTy::get(CheckType->getRecordKeeper())),
+        CheckType(CheckType), Expr(Expr) {}
+
+public:
+  ExistsOpInit(const ExistsOpInit &) = delete;
+  ExistsOpInit &operator=(const ExistsOpInit &) = delete;
+
+  static bool classof(const Init *I) { return I->getKind() == IK_ExistsOpInit; }
+
+  static ExistsOpInit *get(RecTy *CheckType, Init *Expr);
+
+  void Profile(FoldingSetNodeID &ID) const;
+
+  // Fold - If possible, fold this to a simpler init.  Return this if not
+  // possible to fold.
+  Init *Fold(Record *CurRec, bool IsFinal = false) const;
+
+  bool isComplete() const override { return false; }
+
+  Init *resolveReferences(Resolver &R) const override;
+
+  Init *getBit(unsigned Bit) const override;
+
+  std::string getAsString() const override;
+};
+
 /// 'Opcode' - Represent a reference to an entire variable object.
 class VarInit : public TypedInit {
   Init *VarName;

diff  --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index ef012332cd155..e100251fbfcd5 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -79,6 +79,7 @@ struct RecordKeeperImpl {
   FoldingSet<TernOpInit> TheTernOpInitPool;
   FoldingSet<FoldOpInit> TheFoldOpInitPool;
   FoldingSet<IsAOpInit> TheIsAOpInitPool;
+  FoldingSet<ExistsOpInit> TheExistsOpInitPool;
   DenseMap<std::pair<RecTy *, Init *>, VarInit *> TheVarInitPool;
   DenseMap<std::pair<TypedInit *, unsigned>, VarBitInit *> TheVarBitInitPool;
   DenseMap<std::pair<TypedInit *, unsigned>, VarListElementInit *>
@@ -1660,6 +1661,81 @@ std::string IsAOpInit::getAsString() const {
       .str();
 }
 
+static void ProfileExistsOpInit(FoldingSetNodeID &ID, RecTy *CheckType,
+                                Init *Expr) {
+  ID.AddPointer(CheckType);
+  ID.AddPointer(Expr);
+}
+
+ExistsOpInit *ExistsOpInit::get(RecTy *CheckType, Init *Expr) {
+  FoldingSetNodeID ID;
+  ProfileExistsOpInit(ID, CheckType, Expr);
+
+  detail::RecordKeeperImpl &RK = Expr->getRecordKeeper().getImpl();
+  void *IP = nullptr;
+  if (ExistsOpInit *I = RK.TheExistsOpInitPool.FindNodeOrInsertPos(ID, IP))
+    return I;
+
+  ExistsOpInit *I = new (RK.Allocator) ExistsOpInit(CheckType, Expr);
+  RK.TheExistsOpInitPool.InsertNode(I, IP);
+  return I;
+}
+
+void ExistsOpInit::Profile(FoldingSetNodeID &ID) const {
+  ProfileExistsOpInit(ID, CheckType, Expr);
+}
+
+Init *ExistsOpInit::Fold(Record *CurRec, bool IsFinal) const {
+  if (StringInit *Name = dyn_cast<StringInit>(Expr)) {
+    if (!CurRec && !IsFinal)
+      return const_cast<ExistsOpInit *>(this);
+
+    // Self-references are allowed, but their resolution is delayed until
+    // the final resolve to ensure that we get the correct type for them.
+    auto *Anonymous = dyn_cast<AnonymousNameInit>(CurRec->getNameInit());
+    if (Name == CurRec->getNameInit() ||
+        (Anonymous && Name == Anonymous->getNameInit())) {
+      if (!IsFinal)
+        return const_cast<ExistsOpInit *>(this);
+
+      // No doubt that there exists a record, so we should check if types are
+      // compatiable.
+      return IntInit::get(getRecordKeeper(),
+                          CurRec->getType()->typeIsA(CheckType));
+    }
+
+    // Look up all defined records to see if we can find one.
+    Record *D = CheckType->getRecordKeeper().getDef(Name->getValue());
+    if (!D) {
+      if (IsFinal)
+        return IntInit::get(getRecordKeeper(), 0);
+      return const_cast<ExistsOpInit *>(this);
+    }
+
+    // Check if types are compatiable.
+    return IntInit::get(getRecordKeeper(),
+                        DefInit::get(D)->getType()->typeIsA(CheckType));
+  }
+  return const_cast<ExistsOpInit *>(this);
+}
+
+Init *ExistsOpInit::resolveReferences(Resolver &R) const {
+  Init *NewExpr = Expr->resolveReferences(R);
+  if (Expr != NewExpr || R.isFinal())
+    return get(CheckType, NewExpr)->Fold(R.getCurrentRecord(), R.isFinal());
+  return const_cast<ExistsOpInit *>(this);
+}
+
+Init *ExistsOpInit::getBit(unsigned Bit) const {
+  return VarBitInit::get(const_cast<ExistsOpInit *>(this), Bit);
+}
+
+std::string ExistsOpInit::getAsString() const {
+  return (Twine("!exists<") + CheckType->getAsString() + ">(" +
+          Expr->getAsString() + ")")
+      .str();
+}
+
 RecTy *TypedInit::getFieldType(StringInit *FieldName) const {
   if (RecordRecTy *RecordType = dyn_cast<RecordRecTy>(getType())) {
     for (Record *Rec : RecordType->getClasses()) {

diff  --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp
index d3be6cae0ac8c..2a4ee4473b56c 100644
--- a/llvm/lib/TableGen/TGLexer.cpp
+++ b/llvm/lib/TableGen/TGLexer.cpp
@@ -584,6 +584,7 @@ tgtok::TokKind TGLexer::LexExclaim() {
     .Case("find", tgtok::XFind)
     .Cases("setdagop", "setop", tgtok::XSetDagOp) // !setop is deprecated.
     .Cases("getdagop", "getop", tgtok::XGetDagOp) // !getop is deprecated.
+    .Case("exists", tgtok::XExists)
     .Default(tgtok::Error);
 
   return Kind != tgtok::Error ? Kind : ReturnError(Start-1, "Unknown operator");

diff  --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h
index db05bf0d1ddc5..459ba0f4af643 100644
--- a/llvm/lib/TableGen/TGLexer.h
+++ b/llvm/lib/TableGen/TGLexer.h
@@ -56,6 +56,7 @@ namespace tgtok {
     XListConcat, XListSplat, XStrConcat, XInterleave, XSubstr, XFind, XCast,
     XSubst, XForEach, XFilter, XFoldl, XHead, XTail, XSize, XEmpty, XIf,
     XCond, XEq, XIsA, XDag, XNe, XLe, XLt, XGe, XGt, XSetDagOp, XGetDagOp,
+    XExists,
 
     // Boolean literals.
     TrueVal, FalseVal,

diff  --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index c92ab5da1b076..acf93dc3d7925 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -1096,6 +1096,52 @@ Init *TGParser::ParseOperation(Record *CurRec, RecTy *ItemType) {
     return (IsAOpInit::get(Type, LHS))->Fold();
   }
 
+  case tgtok::XExists: {
+    // Value ::= !exists '<' Type '>' '(' Value ')'
+    Lex.Lex(); // eat the operation
+
+    RecTy *Type = ParseOperatorType();
+    if (!Type)
+      return nullptr;
+
+    if (!consume(tgtok::l_paren)) {
+      TokError("expected '(' after type of !exists");
+      return nullptr;
+    }
+
+    SMLoc ExprLoc = Lex.getLoc();
+    Init *Expr = ParseValue(CurRec);
+    if (!Expr)
+      return nullptr;
+
+    TypedInit *ExprType = dyn_cast<TypedInit>(Expr);
+    if (!ExprType) {
+      Error(ExprLoc, "expected string type argument in !exists operator");
+      return nullptr;
+    }
+
+    RecordRecTy *RecType = dyn_cast<RecordRecTy>(ExprType->getType());
+    if (RecType) {
+      Error(ExprLoc,
+            "expected string type argument in !exists operator, please "
+            "use !isa instead");
+      return nullptr;
+    }
+
+    StringRecTy *SType = dyn_cast<StringRecTy>(ExprType->getType());
+    if (!SType) {
+      Error(ExprLoc, "expected string type argument in !exists operator");
+      return nullptr;
+    }
+
+    if (!consume(tgtok::r_paren)) {
+      TokError("expected ')' in !exists");
+      return nullptr;
+    }
+
+    return (ExistsOpInit::get(Type, Expr))->Fold(CurRec);
+  }
+
   case tgtok::XConcat:
   case tgtok::XADD:
   case tgtok::XSUB:
@@ -2358,6 +2404,7 @@ Init *TGParser::ParseSimpleValue(Record *CurRec, RecTy *ItemType,
   case tgtok::XEmpty:
   case tgtok::XCast:
   case tgtok::XGetDagOp: // Value ::= !unop '(' Value ')'
+  case tgtok::XExists:
   case tgtok::XIsA:
   case tgtok::XConcat:
   case tgtok::XDag:

diff  --git a/llvm/test/TableGen/exists-error-non-string.td b/llvm/test/TableGen/exists-error-non-string.td
new file mode 100644
index 0000000000000..61ee486b9dc67
--- /dev/null
+++ b/llvm/test/TableGen/exists-error-non-string.td
@@ -0,0 +1,8 @@
+// RUN: not llvm-tblgen --no-warn-on-unused-template-args %s 2>&1 | FileCheck %s
+// XFAIL: vg_leak
+
+//CHECK: expected string type argument in !exists operator
+
+class A;
+
+defvar value = !exists<A>(123);

diff  --git a/llvm/test/TableGen/exists-error-record.td b/llvm/test/TableGen/exists-error-record.td
new file mode 100644
index 0000000000000..ad5ab2bc0a44d
--- /dev/null
+++ b/llvm/test/TableGen/exists-error-record.td
@@ -0,0 +1,8 @@
+// RUN: not llvm-tblgen --no-warn-on-unused-template-args %s 2>&1 | FileCheck %s
+// XFAIL: vg_leak
+
+//CHECK: expected string type argument in !exists operator, please use !isa instead
+
+class A;
+def a : A;
+defvar value = !exists<A>(a);

diff  --git a/llvm/test/TableGen/exists-error-uninitialized.td b/llvm/test/TableGen/exists-error-uninitialized.td
new file mode 100644
index 0000000000000..e6442da2ff2af
--- /dev/null
+++ b/llvm/test/TableGen/exists-error-uninitialized.td
@@ -0,0 +1,8 @@
+// RUN: not llvm-tblgen --no-warn-on-unused-template-args %s 2>&1 | FileCheck %s
+// XFAIL: vg_leak
+
+//CHECK: expected string type argument in !exists operator
+
+class A;
+
+defvar value = !exists<A>(?);

diff  --git a/llvm/test/TableGen/exists.td b/llvm/test/TableGen/exists.td
new file mode 100644
index 0000000000000..2153dbf9872d3
--- /dev/null
+++ b/llvm/test/TableGen/exists.td
@@ -0,0 +1,67 @@
+// RUN: llvm-tblgen --no-warn-on-unused-template-args %s | FileCheck %s
+// XFAIL: vg_leak
+
+class A;
+def a0 : A;
+
+class A_check<string name>{
+  int exists = !exists<A>(name);
+}
+
+def a0_exists : A_check<"a0">;
+def a1_missing : A_check<"a1">;
+
+
+// Subclasses are allowed.
+
+class B;
+class SubOfB : B;
+class B_check<string name> {
+  int exists = !exists<B>(name);
+}
+
+def sub : SubOfB;
+
+def sub_exists : B_check<"sub">;
+def a0_is_not_sub_of_B : B_check<"a0">;
+
+
+// Self-references are allowed.
+
+class Self_check<string name> {
+  int exists = !exists<Self_check>(name);
+}
+
+def self_reference : Self_check<"self_reference">; // Self-reference
+// There is no record called `current` in current context though we will define it below.
+def current_missing : Self_check<"current">;
+def current : Self_check<"current">;
+
+// CHECK: def a0_exists {
+// CHECK:   int exists = 1;
+// CHECK: }
+
+// CHECK: def a0_is_not_sub_of_B {
+// CHECK:   int exists = 0;
+// CHECK: }
+
+// CHECK: def a1_missing {
+// CHECK:   int exists = 0;
+// CHECK: }
+
+// CHECK: def current {
+// CHECK:   int exists = 1;
+// CHECK: }
+
+// `current` doesn't exist because we define it below `current_missing`.
+// CHECK: def current_missing {
+// CHECK:   int exists = 0;
+// CHECK: }
+
+// CHECK: def self_reference {
+// CHECK:   int exists = 1;
+// CHECK: }
+
+// CHECK: def sub_exists {
+// CHECK:   int exists = 1;
+// CHECK: }


        


More information about the llvm-commits mailing list