[llvm] 04ed64e - [RISCV][llvm-tblgen] Support conditional definitions using !exists clauses

Philip Reames via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 3 11:30:00 PST 2023


Author: Philip Reames
Date: 2023-03-03T11:29:47-08:00
New Revision: 04ed64e42fb4079d6d406018733936a2fbf9db8f

URL: https://github.com/llvm/llvm-project/commit/04ed64e42fb4079d6d406018733936a2fbf9db8f
DIFF: https://github.com/llvm/llvm-project/commit/04ed64e42fb4079d6d406018733936a2fbf9db8f.diff

LOG: [RISCV][llvm-tblgen] Support conditional definitions using !exists clauses

The core part of this change is an extension to the tablegen language to allow conditional definition of records using if-statements based on !exists conditions.

The RISCV td file change is mostly to illustrate the potential use of conditional definitions. I am deliberately not maximally simplifying in this change to make merging with downstream code (or simply rebasing while this on review) easier.

Some background to make the change understandable.

TableGen does not have an if statement internally. It has if expressions - in the form of TernInitOp with IF opcode - and foreach statements. It implements an if-statement as a foreach which iterates either 0 or 1 times.

Foreach nodes are then evaluated via unrolling inside the parser. Specifically, they are evaluated, at latest, when the outermost multiclass or loop containing them reaches end of scope. The unrolled statements remain (potentially) unresolved after unrolling, but the number of iterations must be known at this point.

An !exists clause can only evaluate at final evaluation. (Specifically, forward references to definitions are allowed - up to the end of the containing scope at least.) The existing code did not set the final flag on the resolver, and thus would leave the !exists clause in an unresolved state. This would then cause an error since we don't know how many iterations on which to unroll the (synthetic) foreach loop.

I chose to only finally-evaluate the condition of the if-expression. This allows us to pick an arm at scope exit without inhibiting definitions in the arm from having self references.

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

Added: 
    

Modified: 
    llvm/lib/TableGen/Record.cpp
    llvm/lib/TableGen/TGParser.cpp
    llvm/lib/Target/RISCV/RISCVScheduleV.td
    llvm/test/TableGen/exists.td

Removed: 
    


################################################################################
diff  --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index 9ea68e2eca51..654857fc19fe 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -1772,34 +1772,34 @@ void ExistsOpInit::Profile(FoldingSetNodeID &ID) const {
 
 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);
+    if (D) {
+      // Check if types are compatiable.
+      return IntInit::get(getRecordKeeper(),
+                          DefInit::get(D)->getType()->typeIsA(CheckType));
     }
 
-    // Check if types are compatiable.
-    return IntInit::get(getRecordKeeper(),
-                        DefInit::get(D)->getType()->typeIsA(CheckType));
+    if (CurRec) {
+      // 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));
+      }
+    }
+
+    if (IsFinal)
+      return IntInit::get(getRecordKeeper(), 0);
+    return const_cast<ExistsOpInit *>(this);
   }
   return const_cast<ExistsOpInit *>(this);
 }

diff  --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index 7fc46a8b4a87..735e565fc417 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -384,10 +384,35 @@ bool TGParser::addEntry(RecordsEntry E) {
 bool TGParser::resolve(const ForeachLoop &Loop, SubstStack &Substs,
                        bool Final, std::vector<RecordsEntry> *Dest,
                        SMLoc *Loc) {
+
   MapResolver R;
   for (const auto &S : Substs)
     R.set(S.first, S.second);
   Init *List = Loop.ListValue->resolveReferences(R);
+
+  // For if-then-else blocks, we lower to a foreach loop whose list is a
+  // ternary selection between lists of 
diff erent length.  Since we don't
+  // have a means to track variable length record lists, we *must* resolve
+  // the condition here.  We want to defer final resolution of the arms
+  // until the resulting records are finalized.
+  // e.g. !if(!exists<SchedWrite>("__does_not_exist__"), [1], [])
+  if (auto *TI = dyn_cast<TernOpInit>(List);
+      TI && TI->getOpcode() == TernOpInit::IF && Final) {
+    Init *OldLHS = TI->getLHS();
+    R.setFinal(true);
+    Init *LHS = OldLHS->resolveReferences(R);
+    if (LHS == OldLHS) {
+      PrintError(Loop.Loc,
+                 Twine("unable to resolve if condition '") +
+                 LHS->getAsString() + "' at end of containing scope");
+      return true;
+    }
+    Init *MHS = TI->getMHS();
+    Init *RHS = TI->getRHS();
+    List = TernOpInit::get(TernOpInit::IF, LHS, MHS, RHS, TI->getType())
+      ->Fold(nullptr);
+  }
+
   auto LI = dyn_cast<ListInit>(List);
   if (!LI) {
     if (!Final) {

diff  --git a/llvm/lib/Target/RISCV/RISCVScheduleV.td b/llvm/lib/Target/RISCV/RISCVScheduleV.td
index 5f01dabd2802..ad3d4cdcbfdf 100644
--- a/llvm/lib/Target/RISCV/RISCVScheduleV.td
+++ b/llvm/lib/Target/RISCV/RISCVScheduleV.td
@@ -31,43 +31,44 @@ multiclass LMULSchedReadsImpl<string name, list<string> MxList> {
     def name # "_" # mx : SchedRead;
   }
 }
-multiclass LMULWriteResImpl<string name, list<string> MxList,
-                            list<ProcResourceKind> resources> {
-  foreach mx = MxList in {
-    def : WriteRes<!cast<SchedWrite>(name # "_" # mx), resources>;
+multiclass LMULWriteResImpl<string name, list<ProcResourceKind> resources> {
+  foreach mx = SchedMxList in {
+    if !exists<SchedWrite>(name # "_" # mx) then
+      def : WriteRes<!cast<SchedWrite>(name # "_" # mx), resources>;
   }
 }
-multiclass LMULReadAdvanceImpl<string name, list<string> MxList, int val,
+multiclass LMULReadAdvanceImpl<string name, int val,
                                list<SchedWrite> writes = []> {
-  foreach mx = MxList in {
-    def : ReadAdvance<!cast<SchedRead>(name # "_" # mx), val, writes>;
+  foreach mx = SchedMxList in {
+    if !exists<SchedRead>(name # "_" # mx) then
+      def : ReadAdvance<!cast<SchedRead>(name # "_" # mx), val, writes>;
   }
 }
 
 multiclass LMULSchedWrites<string name> : LMULSchedWritesImpl<name, SchedMxList>;
 multiclass LMULSchedReads<string name> : LMULSchedReadsImpl<name, SchedMxList>;
 multiclass LMULWriteRes<string name, list<ProcResourceKind> resources>
-  : LMULWriteResImpl<name, SchedMxList, resources>;
+  : LMULWriteResImpl<name, resources>;
 multiclass LMULReadAdvance<string name, int val, list<SchedWrite> writes = []>
-  : LMULReadAdvanceImpl<name, SchedMxList, val, writes>;
+  : LMULReadAdvanceImpl<name, val, writes>;
 
 multiclass LMULSchedWritesW<string name> : LMULSchedWritesImpl<name, SchedMxListW>;
 multiclass LMULSchedReadsW<string name> : LMULSchedReadsImpl<name, SchedMxListW>;
 multiclass LMULWriteResW<string name, list<ProcResourceKind> resources>
-  : LMULWriteResImpl<name, SchedMxListW, resources>;
+  : LMULWriteResImpl<name, resources>;
 multiclass LMULReadAdvanceW<string name, int val, list<SchedWrite> writes = []>
-  : LMULReadAdvanceImpl<name, SchedMxListW, val, writes>;
+  : LMULReadAdvanceImpl<name, val, writes>;
 
 multiclass LMULSchedWritesFW<string name> : LMULSchedWritesImpl<name, SchedMxListFW>;
 multiclass LMULSchedReadsFW<string name> : LMULSchedReadsImpl<name, SchedMxListFW>;
 multiclass LMULWriteResFW<string name, list<ProcResourceKind> resources>
-  : LMULWriteResImpl<name, SchedMxListFW, resources>;
+  : LMULWriteResImpl<name, resources>;
 multiclass LMULReadAdvanceFW<string name, int val, list<SchedWrite> writes = []>
-  : LMULReadAdvanceImpl<name, SchedMxListFW, val, writes>;
+  : LMULReadAdvanceImpl<name, val, writes>;
 
 multiclass LMULSchedWritesFWRed<string name> : LMULSchedWritesImpl<name, SchedMxListFWRed>;
 multiclass LMULWriteResFWRed<string name, list<ProcResourceKind> resources>
-  : LMULWriteResImpl<name, SchedMxListFWRed, resources>;
+  : LMULWriteResImpl<name, resources>;
 
 
 // 3.6 Vector Byte Length vlenb

diff  --git a/llvm/test/TableGen/exists.td b/llvm/test/TableGen/exists.td
index 2153dbf9872d..ee68b452ab92 100644
--- a/llvm/test/TableGen/exists.td
+++ b/llvm/test/TableGen/exists.td
@@ -37,6 +37,30 @@ def self_reference : Self_check<"self_reference">; // Self-reference
 def current_missing : Self_check<"current">;
 def current : Self_check<"current">;
 
+
+// Check that conditional definitions dependent on the resolution of an
+// exists clause work as expected.
+// Reminder: a0 exists, a1 does not.
+class C {
+  int exists = 1;
+}
+if !exists<A>("a0") then
+   def if_exists : C;
+if !exists<A>("a1") then
+   def if_no_exists: C;
+foreach e = ["a0", "a1"] in {
+  if !exists<A>(e) then
+      def for_exists_ # e: C;
+}
+multiclass mc {
+  foreach e = ["a0", "a1"] in {
+    if !exists<A>(e) then
+        def _ # e: C;
+  }
+}
+defm multiclass_exists : mc<>;
+
+
 // CHECK: def a0_exists {
 // CHECK:   int exists = 1;
 // CHECK: }
@@ -58,6 +82,16 @@ def current : Self_check<"current">;
 // CHECK:   int exists = 0;
 // CHECK: }
 
+// CHECK: def for_exists_a0 {
+// CHECK:   int exists = 1;
+// CHECK: }
+// CHECK: def if_exists {
+// CHECK:   int exists = 1;
+// CHECK: }
+// CHECK: def multiclass_exists_a0 {
+// CHECK:   int exists = 1;
+// CHECK: }
+
 // CHECK: def self_reference {
 // CHECK:   int exists = 1;
 // CHECK: }


        


More information about the llvm-commits mailing list