1 //===- GlobalCombinerEmitter.cpp - Generate a combiner --------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 /// \file Generate a combiner implementation for GlobalISel from a declarative
10 /// syntax
11 ///
12 //===----------------------------------------------------------------------===//
13 
14 #include "llvm/ADT/Statistic.h"
15 #include "llvm/Support/CommandLine.h"
16 #include "llvm/Support/Timer.h"
17 #include "llvm/TableGen/Error.h"
18 #include "llvm/TableGen/StringMatcher.h"
19 #include "llvm/TableGen/TableGenBackend.h"
20 #include "CodeGenTarget.h"
21 #include "GlobalISel/CodeExpander.h"
22 #include "GlobalISel/CodeExpansions.h"
23 
24 using namespace llvm;
25 
26 #define DEBUG_TYPE "gicombiner-emitter"
27 
28 // FIXME: Use ALWAYS_ENABLED_STATISTIC once it's available.
29 unsigned NumPatternTotal = 0;
30 STATISTIC(NumPatternTotalStatistic, "Total number of patterns");
31 
32 cl::OptionCategory
33     GICombinerEmitterCat("Options for -gen-global-isel-combiner");
34 static cl::list<std::string>
35     SelectedCombiners("combiners", cl::desc("Emit the specified combiners"),
36                       cl::cat(GICombinerEmitterCat), cl::CommaSeparated);
37 static cl::opt<bool> ShowExpansions(
38     "gicombiner-show-expansions",
39     cl::desc("Use C++ comments to indicate occurence of code expansion"),
40     cl::cat(GICombinerEmitterCat));
41 
42 namespace {
43 typedef uint64_t RuleID;
44 
45 class RootInfo {
46   StringRef PatternSymbol;
47 
48 public:
49   RootInfo(StringRef PatternSymbol) : PatternSymbol(PatternSymbol) {}
50 
51   StringRef getPatternSymbol() const { return PatternSymbol; }
52 };
53 
54 class CombineRule {
55 protected:
56   /// A unique ID for this rule
57   /// ID's are used for debugging and run-time disabling of rules among other
58   /// things.
59   RuleID ID;
60 
61   /// The record defining this rule.
62   const Record &TheDef;
63 
64   /// The roots of a match. These are the leaves of the DAG that are closest to
65   /// the end of the function. I.e. the nodes that are encountered without
66   /// following any edges of the DAG described by the pattern as we work our way
67   /// from the bottom of the function to the top.
68   std::vector<RootInfo> Roots;
69 
70   /// A block of arbitrary C++ to finish testing the match.
71   /// FIXME: This is a temporary measure until we have actual pattern matching
72   const CodeInit *MatchingFixupCode = nullptr;
73 public:
74   CombineRule(const CodeGenTarget &Target, RuleID ID, const Record &R)
75       : ID(ID), TheDef(R) {}
76   bool parseDefs();
77   bool parseMatcher(const CodeGenTarget &Target);
78 
79   RuleID getID() const { return ID; }
80   StringRef getName() const { return TheDef.getName(); }
81   const Record &getDef() const { return TheDef; }
82   const CodeInit *getMatchingFixupCode() const { return MatchingFixupCode; }
83   size_t getNumRoots() const { return Roots.size(); }
84 
85   using const_root_iterator = std::vector<RootInfo>::const_iterator;
86   const_root_iterator roots_begin() const { return Roots.begin(); }
87   const_root_iterator roots_end() const { return Roots.end(); }
88   iterator_range<const_root_iterator> roots() const {
89     return llvm::make_range(Roots.begin(), Roots.end());
90   }
91 };
92 
93 /// A convenience function to check that an Init refers to a specific def. This
94 /// is primarily useful for testing for defs and similar in DagInit's since
95 /// DagInit's support any type inside them.
96 static bool isSpecificDef(const Init &N, StringRef Def) {
97   if (const DefInit *OpI = dyn_cast<DefInit>(&N))
98     if (OpI->getDef()->getName() == Def)
99       return true;
100   return false;
101 }
102 
103 /// A convenience function to check that an Init refers to a def that is a
104 /// subclass of the given class and coerce it to a def if it is. This is
105 /// primarily useful for testing for subclasses of GIMatchKind and similar in
106 /// DagInit's since DagInit's support any type inside them.
107 static Record *getDefOfSubClass(const Init &N, StringRef Cls) {
108   if (const DefInit *OpI = dyn_cast<DefInit>(&N))
109     if (OpI->getDef()->isSubClassOf(Cls))
110       return OpI->getDef();
111   return nullptr;
112 }
113 
114 bool CombineRule::parseDefs() {
115   NamedRegionTimer T("parseDefs", "Time spent parsing the defs", "Rule Parsing",
116                      "Time spent on rule parsing", TimeRegions);
117   DagInit *Defs = TheDef.getValueAsDag("Defs");
118 
119   if (Defs->getOperatorAsDef(TheDef.getLoc())->getName() != "defs") {
120     PrintError(TheDef.getLoc(), "Expected defs operator");
121     return false;
122   }
123 
124   for (unsigned I = 0, E = Defs->getNumArgs(); I < E; ++I) {
125     // Roots should be collected into Roots
126     if (isSpecificDef(*Defs->getArg(I), "root")) {
127       Roots.emplace_back(Defs->getArgNameStr(I));
128       continue;
129     }
130 
131     // Otherwise emit an appropriate error message.
132     if (getDefOfSubClass(*Defs->getArg(I), "GIDefKind"))
133       PrintError(TheDef.getLoc(),
134                  "This GIDefKind not implemented in tablegen");
135     else if (getDefOfSubClass(*Defs->getArg(I), "GIDefKindWithArgs"))
136       PrintError(TheDef.getLoc(),
137                  "This GIDefKindWithArgs not implemented in tablegen");
138     else
139       PrintError(TheDef.getLoc(),
140                  "Expected a subclass of GIDefKind or a sub-dag whose "
141                  "operator is of type GIDefKindWithArgs");
142     return false;
143   }
144 
145   if (Roots.empty()) {
146     PrintError(TheDef.getLoc(), "Combine rules must have at least one root");
147     return false;
148   }
149   return true;
150 }
151 
152 bool CombineRule::parseMatcher(const CodeGenTarget &Target) {
153   NamedRegionTimer T("parseMatcher", "Time spent parsing the matcher",
154                      "Rule Parsing", "Time spent on rule parsing", TimeRegions);
155   DagInit *Matchers = TheDef.getValueAsDag("Match");
156 
157   if (Matchers->getOperatorAsDef(TheDef.getLoc())->getName() != "match") {
158     PrintError(TheDef.getLoc(), "Expected match operator");
159     return false;
160   }
161 
162   if (Matchers->getNumArgs() == 0) {
163     PrintError(TheDef.getLoc(), "Matcher is empty");
164     return false;
165   }
166 
167   // The match section consists of a list of matchers and predicates. Parse each
168   // one and add the equivalent GIMatchDag nodes, predicates, and edges.
169   for (unsigned I = 0; I < Matchers->getNumArgs(); ++I) {
170 
171     // Parse arbitrary C++ code we have in lieu of supporting MIR matching
172     if (const CodeInit *CodeI = dyn_cast<CodeInit>(Matchers->getArg(I))) {
173       assert(!MatchingFixupCode &&
174              "Only one block of arbitrary code is currently permitted");
175       MatchingFixupCode = CodeI;
176       continue;
177     }
178 
179     PrintError(TheDef.getLoc(),
180                "Expected a subclass of GIMatchKind or a sub-dag whose "
181                "operator is either of a GIMatchKindWithArgs or Instruction");
182     PrintNote("Pattern was `" + Matchers->getArg(I)->getAsString() + "'");
183     return false;
184   }
185   return true;
186 }
187 
188 class GICombinerEmitter {
189   StringRef Name;
190   const CodeGenTarget &Target;
191   Record *Combiner;
192   std::vector<std::unique_ptr<CombineRule>> Rules;
193   std::unique_ptr<CombineRule> makeCombineRule(const Record &R);
194 
195   void gatherRules(std::vector<std::unique_ptr<CombineRule>> &ActiveRules,
196                    const std::vector<Record *> &&RulesAndGroups);
197 
198 public:
199   explicit GICombinerEmitter(RecordKeeper &RK, const CodeGenTarget &Target,
200                              StringRef Name, Record *Combiner);
201   ~GICombinerEmitter() {}
202 
203   StringRef getClassName() const {
204     return Combiner->getValueAsString("Classname");
205   }
206   void run(raw_ostream &OS);
207 
208   /// Emit the name matcher (guarded by #ifndef NDEBUG) used to disable rules in
209   /// response to the generated cl::opt.
210   void emitNameMatcher(raw_ostream &OS) const;
211   void generateCodeForRule(raw_ostream &OS, const CombineRule *Rule,
212                            StringRef Indent) const;
213 };
214 
215 GICombinerEmitter::GICombinerEmitter(RecordKeeper &RK,
216                                      const CodeGenTarget &Target,
217                                      StringRef Name, Record *Combiner)
218     : Name(Name), Target(Target), Combiner(Combiner) {}
219 
220 void GICombinerEmitter::emitNameMatcher(raw_ostream &OS) const {
221   std::vector<std::pair<std::string, std::string>> Cases;
222   Cases.reserve(Rules.size());
223 
224   for (const CombineRule &EnumeratedRule : make_pointee_range(Rules)) {
225     std::string Code;
226     raw_string_ostream SS(Code);
227     SS << "return " << EnumeratedRule.getID() << ";\n";
228     Cases.push_back(std::make_pair(EnumeratedRule.getName(), SS.str()));
229   }
230 
231   OS << "static Optional<uint64_t> getRuleIdxForIdentifier(StringRef "
232         "RuleIdentifier) {\n"
233      << "  uint64_t I;\n"
234      << "  // getAtInteger(...) returns false on success\n"
235      << "  bool Parsed = !RuleIdentifier.getAsInteger(0, I);\n"
236      << "  if (Parsed)\n"
237      << "    return I;\n\n"
238      << "#ifndef NDEBUG\n";
239   StringMatcher Matcher("RuleIdentifier", Cases, OS);
240   Matcher.Emit();
241   OS << "#endif // ifndef NDEBUG\n\n"
242      << "  return None;\n"
243      << "}\n";
244 }
245 
246 std::unique_ptr<CombineRule>
247 GICombinerEmitter::makeCombineRule(const Record &TheDef) {
248   std::unique_ptr<CombineRule> Rule =
249       std::make_unique<CombineRule>(Target, NumPatternTotal, TheDef);
250 
251   if (!Rule->parseDefs())
252     return nullptr;
253   if (!Rule->parseMatcher(Target))
254     return nullptr;
255   // For now, don't support multi-root rules. We'll come back to this later
256   // once we have the algorithm changes to support it.
257   if (Rule->getNumRoots() > 1) {
258     PrintError(TheDef.getLoc(), "Multi-root matches are not supported (yet)");
259     return nullptr;
260   }
261   return Rule;
262 }
263 
264 /// Recurse into GICombineGroup's and flatten the ruleset into a simple list.
265 void GICombinerEmitter::gatherRules(
266     std::vector<std::unique_ptr<CombineRule>> &ActiveRules,
267     const std::vector<Record *> &&RulesAndGroups) {
268   for (Record *R : RulesAndGroups) {
269     if (R->isValueUnset("Rules")) {
270       std::unique_ptr<CombineRule> Rule = makeCombineRule(*R);
271       if (Rule == nullptr) {
272         PrintError(R->getLoc(), "Failed to parse rule");
273         continue;
274       }
275       ActiveRules.emplace_back(std::move(Rule));
276       ++NumPatternTotal;
277     } else
278       gatherRules(ActiveRules, R->getValueAsListOfDefs("Rules"));
279   }
280 }
281 
282 void GICombinerEmitter::generateCodeForRule(raw_ostream &OS,
283                                             const CombineRule *Rule,
284                                             StringRef Indent) const {
285   {
286     const Record &RuleDef = Rule->getDef();
287 
288     OS << Indent << "// Rule: " << RuleDef.getName() << "\n"
289        << Indent << "if (!isRuleDisabled(" << Rule->getID() << ")) {\n";
290 
291     CodeExpansions Expansions;
292     for (const RootInfo &Root : Rule->roots()) {
293       Expansions.declare(Root.getPatternSymbol(), "MI");
294     }
295     DagInit *Applyer = RuleDef.getValueAsDag("Apply");
296     if (Applyer->getOperatorAsDef(RuleDef.getLoc())->getName() !=
297         "apply") {
298       PrintError(RuleDef.getLoc(), "Expected apply operator");
299       return;
300     }
301 
302     OS << Indent << "  if (1\n";
303 
304     if (Rule->getMatchingFixupCode() &&
305         !Rule->getMatchingFixupCode()->getValue().empty()) {
306       // FIXME: Single-use lambda's like this are a serious compile-time
307       // performance and memory issue. It's convenient for this early stage to
308       // defer some work to successive patches but we need to eliminate this
309       // before the ruleset grows to small-moderate size. Last time, it became
310       // a big problem for low-mem systems around the 500 rule mark but by the
311       // time we grow that large we should have merged the ISel match table
312       // mechanism with the Combiner.
313       OS << Indent << "      && [&]() {\n"
314          << Indent << "      "
315          << CodeExpander(Rule->getMatchingFixupCode()->getValue(), Expansions,
316                          Rule->getMatchingFixupCode()->getLoc(), ShowExpansions)
317          << "\n"
318          << Indent << "      return true;\n"
319          << Indent << "  }()";
320     }
321     OS << ") {\n" << Indent << "   ";
322 
323     if (const CodeInit *Code = dyn_cast<CodeInit>(Applyer->getArg(0))) {
324       OS << CodeExpander(Code->getAsUnquotedString(), Expansions,
325                          Code->getLoc(), ShowExpansions)
326          << "\n"
327          << Indent << "    return true;\n"
328          << Indent << "  }\n";
329     } else {
330       PrintError(RuleDef.getLoc(), "Expected apply code block");
331       return;
332     }
333 
334     OS << Indent << "}\n";
335   }
336 }
337 
338 void GICombinerEmitter::run(raw_ostream &OS) {
339   gatherRules(Rules, Combiner->getValueAsListOfDefs("Rules"));
340   if (ErrorsPrinted)
341     PrintFatalError(Combiner->getLoc(), "Failed to parse one or more rules");
342 
343   NamedRegionTimer T("Emit", "Time spent emitting the combiner",
344                      "Code Generation", "Time spent generating code",
345                      TimeRegions);
346   OS << "#ifdef " << Name.upper() << "_GENCOMBINERHELPER_DEPS\n"
347      << "#include \"llvm/ADT/SparseBitVector.h\"\n"
348      << "namespace llvm {\n"
349      << "extern cl::OptionCategory GICombinerOptionCategory;\n"
350      << "} // end namespace llvm\n"
351      << "#endif // ifdef " << Name.upper() << "_GENCOMBINERHELPER_DEPS\n\n";
352 
353   OS << "#ifdef " << Name.upper() << "_GENCOMBINERHELPER_H\n"
354      << "class " << getClassName() << " {\n"
355      << "  SparseBitVector<> DisabledRules;\n"
356      << "\n"
357      << "public:\n"
358      << "  bool parseCommandLineOption();\n"
359      << "  bool isRuleDisabled(unsigned ID) const;\n"
360      << "  bool setRuleDisabled(StringRef RuleIdentifier);\n"
361      << "\n"
362      << "  bool tryCombineAll(\n"
363      << "    GISelChangeObserver &Observer,\n"
364      << "    MachineInstr &MI,\n"
365      << "    MachineIRBuilder &B) const;\n"
366      << "};\n\n";
367 
368   emitNameMatcher(OS);
369 
370   OS << "bool " << getClassName()
371      << "::setRuleDisabled(StringRef RuleIdentifier) {\n"
372      << "  std::pair<StringRef, StringRef> RangePair = "
373         "RuleIdentifier.split('-');\n"
374      << "  if (!RangePair.second.empty()) {\n"
375      << "    const auto First = getRuleIdxForIdentifier(RangePair.first);\n"
376      << "    const auto Last = getRuleIdxForIdentifier(RangePair.second);\n"
377      << "    if (!First.hasValue() || !Last.hasValue())\n"
378      << "      return false;\n"
379      << "    if (First >= Last)\n"
380      << "      report_fatal_error(\"Beginning of range should be before end of "
381         "range\");\n"
382      << "    for (auto I = First.getValue(); I < Last.getValue(); ++I)\n"
383      << "      DisabledRules.set(I);\n"
384      << "    return true;\n"
385      << "  } else {\n"
386      << "    const auto I = getRuleIdxForIdentifier(RangePair.first);\n"
387      << "    if (!I.hasValue())\n"
388      << "      return false;\n"
389      << "    DisabledRules.set(I.getValue());\n"
390      << "    return true;\n"
391      << "  }\n"
392      << "  return false;\n"
393      << "}\n";
394 
395   OS << "bool " << getClassName()
396      << "::isRuleDisabled(unsigned RuleID) const {\n"
397      << "  return DisabledRules.test(RuleID);\n"
398      << "}\n";
399   OS << "#endif // ifdef " << Name.upper() << "_GENCOMBINERHELPER_H\n\n";
400 
401   OS << "#ifdef " << Name.upper() << "_GENCOMBINERHELPER_CPP\n"
402      << "\n"
403      << "cl::list<std::string> " << Name << "Option(\n"
404      << "    \"" << Name.lower() << "-disable-rule\",\n"
405      << "    cl::desc(\"Disable one or more combiner rules temporarily in "
406      << "the " << Name << " pass\"),\n"
407      << "    cl::CommaSeparated,\n"
408      << "    cl::Hidden,\n"
409      << "    cl::cat(GICombinerOptionCategory));\n"
410      << "\n"
411      << "bool " << getClassName() << "::parseCommandLineOption() {\n"
412      << "  for (const auto &Identifier : " << Name << "Option)\n"
413      << "    if (!setRuleDisabled(Identifier))\n"
414      << "      return false;\n"
415      << "  return true;\n"
416      << "}\n\n";
417 
418   OS << "bool " << getClassName() << "::tryCombineAll(\n"
419      << "    GISelChangeObserver &Observer,\n"
420      << "    MachineInstr &MI,\n"
421      << "    MachineIRBuilder &B) const {\n"
422      << "  CombinerHelper Helper(Observer, B);\n"
423      << "  MachineBasicBlock *MBB = MI.getParent();\n"
424      << "  MachineFunction *MF = MBB->getParent();\n"
425      << "  MachineRegisterInfo &MRI = MF->getRegInfo();\n"
426      << "  (void)MBB; (void)MF; (void)MRI;\n\n";
427 
428   for (const auto &Rule : Rules)
429     generateCodeForRule(OS, Rule.get(), "  ");
430   OS << "\n  return false;\n"
431      << "}\n"
432      << "#endif // ifdef " << Name.upper() << "_GENCOMBINERHELPER_CPP\n";
433 }
434 
435 } // end anonymous namespace
436 
437 //===----------------------------------------------------------------------===//
438 
439 namespace llvm {
440 void EmitGICombiner(RecordKeeper &RK, raw_ostream &OS) {
441   CodeGenTarget Target(RK);
442   emitSourceFileHeader("Global Combiner", OS);
443 
444   if (SelectedCombiners.empty())
445     PrintFatalError("No combiners selected with -combiners");
446   for (const auto &Combiner : SelectedCombiners) {
447     Record *CombinerDef = RK.getDef(Combiner);
448     if (!CombinerDef)
449       PrintFatalError("Could not find " + Combiner);
450     GICombinerEmitter(RK, Target, Combiner, CombinerDef).run(OS);
451   }
452   NumPatternTotalStatistic = NumPatternTotal;
453 }
454 
455 } // namespace llvm
456