[llvm] [TableGen] Precompute tree depths of asm operand classes (PR #71059)

Tomáš Zeman via llvm-commits llvm-commits at lists.llvm.org
Thu Nov 2 05:53:18 PDT 2023


https://github.com/tee-jay-zet created https://github.com/llvm/llvm-project/pull/71059

User-defined AsmOperandClass instances need to declare their
superclasses. This information is used to sort these classes
topologically, but it is used in an indirect fashion: the sorting
algorithm computes the left-biased depth of each class in the DAG of
user-defined classes, and it sorts the deeper classes first.

This sorting algorithm is not guaranteed to produce a correct
topological sort (see the poset in UserOperandClassSorting.td for an
example where it fails). It does work under the assumption that the
left-biased path is a longest path from the given class to a maximal
class.

This patch adds a field called TreeDepth of type std::optional<int> in
ClassInfo. This field is precomputed for all instances of ClassInfo
which are compared using ClassInfo::getTreeDepth(), namely tokens and
user-defined classes, and is guaranteed to contain the length of a
longest path to a maximal class. getTreeDepth is now merely a getter for
this value.


>From f08e01abf7e23ea99bb78289dd498c5e4365ce23 Mon Sep 17 00:00:00 2001
From: Tomas Zeman <tomas.zeman at ericsson.com>
Date: Thu, 2 Nov 2023 13:32:35 +0100
Subject: [PATCH] [TableGen] Precompute tree depths of asm operand classes

User-defined AsmOperandClass instances need to declare their
superclasses. This information is used to sort these classes
topologically, but it is used in an indirect fashion: the sorting
algorithm computes the left-biased depth of each class in the DAG of
user-defined classes, and it sorts the deeper classes first.

This sorting algorithm is not guaranteed to produce a correct
topological sort (see the poset in UserOperandClassSorting.td for an
example where it fails). It does work under the assumption that the
left-biased path is a longest path from the given class to a maximal
class.

This patch adds a field called TreeDepth of type std::optional<int> in
ClassInfo. This field is precomputed for all instances of ClassInfo
which are compared using ClassInfo::getTreeDepth(), namely tokens and
user-defined classes, and is guaranteed to contain the length of a
longest path to a maximal class. getTreeDepth is now merely a getter for
this value.
---
 llvm/test/TableGen/UserOperandClassSorting.td | 49 +++++++++++++++++++
 llvm/utils/TableGen/AsmMatcherEmitter.cpp     | 31 +++++++++---
 2 files changed, 73 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/TableGen/UserOperandClassSorting.td

diff --git a/llvm/test/TableGen/UserOperandClassSorting.td b/llvm/test/TableGen/UserOperandClassSorting.td
new file mode 100644
index 000000000000000..1e888f7e9fd6ae5
--- /dev/null
+++ b/llvm/test/TableGen/UserOperandClassSorting.td
@@ -0,0 +1,49 @@
+// RUN: llvm-tblgen -gen-asm-matcher -I %p/../../include %s | FileCheck %s
+
+/* We test the following poset of user-defined asm operand classes:
+ *
+ *                          Top
+ *                         /   \
+ *                        /   Right1
+ *                       /      |
+ *                     Left   Right2
+ *                       \      |
+ *                        \   Right3
+ *                         \   /
+ *                        Bottom
+ *
+ * With this poset, Bottom should be sorted before Right3, but its left-biased
+ * depth is only 2 while the left-biased depth of Right3 is 3. Thus with a
+ * careless use of left-biased depth ordering, we might end up sorting Bottom
+ * after Right3, which will fail EXPENSIVE_CHECKS.
+ */
+
+include "llvm/Target/Target.td"
+
+def ArchInstrInfo : InstrInfo;
+
+def Arch : Target {
+  let InstructionSet = ArchInstrInfo;
+}
+
+def DummyReg : Register<"dummy">;
+def DummyRC : RegisterClass<"Arch", [i16], 16, (add DummyReg)>;
+
+class AOC<string N, list<AsmOperandClass> supers = []> : AsmOperandClass {
+  let Name = N;
+  let SuperClasses = supers;
+}
+
+def Top : AOC<"Top">;
+def Left : AOC<"Left", [Top]>;
+def Right1 : AOC<"Right1", [Top]>;
+def Right2 : AOC<"Right2", [Right1]>;
+def Right3 : AOC<"Right3", [Right2]>;
+def Bottom : AOC<"Bottom", [Left, Right3]>;
+
+// The MatchClassKind enum is emitted in the topsort order, so it's a good proxy:
+// CHECK:     enum MatchClassKind {
+// CHECK-NOT: };
+// CHECK:     MCK_Bottom,
+// CHECK-NOT: };
+// CHECK:     MCK_Right3,
diff --git a/llvm/utils/TableGen/AsmMatcherEmitter.cpp b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
index 6231f5530d35146..9874f6d72ad58f6 100644
--- a/llvm/utils/TableGen/AsmMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
@@ -178,6 +178,11 @@ struct ClassInfo {
   /// operands include all superclasses.
   std::vector<ClassInfo*> SuperClasses;
 
+  /// TreeDepth - The maximum depth of this class in the poset of classes. This
+  /// field is only computed and set for tokens and user-defined classes to
+  /// speed up class sorting.
+  std::optional<int> TreeDepth;
+
   /// Name - The full class name, suitable for use in an enum.
   std::string Name;
 
@@ -290,13 +295,9 @@ struct ClassInfo {
   }
 
   int getTreeDepth() const {
-    int Depth = 0;
-    const ClassInfo *Root = this;
-    while (!Root->SuperClasses.empty()) {
-      Depth++;
-      Root = Root->SuperClasses.front();
-    }
-    return Depth;
+    assert(TreeDepth.has_value() &&
+           "Attempting to get the TreeDepth of a class that doesn't have one!");
+    return *TreeDepth;
   }
 
   const ClassInfo *findRoot() const {
@@ -306,6 +307,17 @@ struct ClassInfo {
     return Root;
   }
 
+  int computeTreeDepth() {
+    if (TreeDepth.has_value())
+      return *TreeDepth;
+
+    int Depth = 0;
+    for (ClassInfo *Parent : SuperClasses)
+      Depth = std::max(Depth, 1 + Parent->computeTreeDepth());
+    TreeDepth = Depth;
+    return Depth;
+  }
+
   /// Compare two classes. This does not produce a total ordering, but does
   /// guarantee that subclasses are sorted before their parents, and that the
   /// ordering is transitive.
@@ -1665,6 +1677,11 @@ void AsmMatcherInfo::buildInfo() {
     FromClass->SuperClasses.push_back(ToClass);
   }
 
+  // Precompute tree depths for classes which use them for comparisons
+  for (ClassInfo &CI : Classes)
+    if (CI.Kind == ClassInfo::Token || CI.isUserClass())
+      CI.computeTreeDepth();
+
   // Reorder classes so that classes precede super classes.
   Classes.sort();
 



More information about the llvm-commits mailing list