xref: /llvm-project/mlir/tools/mlir-tblgen/OpDocGen.cpp (revision 0be38d4f32d54bc01cea54d8f9e7e9d059620d79)
1 //===- OpDocGen.cpp - MLIR operation documentation generator --------------===//
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 // OpDocGen uses the description of operations to generate documentation for the
10 // operations.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "DocGenUtilities.h"
15 #include "OpGenHelpers.h"
16 #include "mlir/Support/IndentedOstream.h"
17 #include "mlir/TableGen/AttrOrTypeDef.h"
18 #include "mlir/TableGen/GenInfo.h"
19 #include "mlir/TableGen/Operator.h"
20 #include "llvm/ADT/DenseMap.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/Support/CommandLine.h"
23 #include "llvm/Support/FormatVariadic.h"
24 #include "llvm/Support/Regex.h"
25 #include "llvm/Support/Signals.h"
26 #include "llvm/TableGen/Error.h"
27 #include "llvm/TableGen/Record.h"
28 #include "llvm/TableGen/TableGenBackend.h"
29 
30 #include <set>
31 
32 using namespace llvm;
33 using namespace mlir;
34 using namespace mlir::tblgen;
35 
36 using mlir::tblgen::Operator;
37 
38 extern llvm::cl::opt<std::string> selectedDialect;
39 
40 // Emit the description by aligning the text to the left per line (e.g.,
41 // removing the minimum indentation across the block).
42 //
43 // This expects that the description in the tablegen file is already formatted
44 // in a way the user wanted but has some additional indenting due to being
45 // nested in the op definition.
46 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) {
47   raw_indented_ostream ros(os);
48   ros.printReindented(description.rtrim(" \t"));
49 }
50 
51 // Emits `str` with trailing newline if not empty.
52 static void emitIfNotEmpty(StringRef str, raw_ostream &os) {
53   if (!str.empty()) {
54     emitDescription(str, os);
55     os << "\n";
56   }
57 }
58 
59 /// Emit the given named constraint.
60 template <typename T>
61 static void emitNamedConstraint(const T &it, raw_ostream &os) {
62   if (!it.name.empty())
63     os << "| `" << it.name << "`";
64   else
65     os << "&laquo;unnamed&raquo;";
66   os << " | " << it.constraint.getSummary() << "\n";
67 }
68 
69 //===----------------------------------------------------------------------===//
70 // Operation Documentation
71 //===----------------------------------------------------------------------===//
72 
73 /// Emit the assembly format of an operation.
74 static void emitAssemblyFormat(StringRef opName, StringRef format,
75                                raw_ostream &os) {
76   os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` ";
77 
78   // Print the assembly format aligned.
79   unsigned indent = strlen("operation ::= ");
80   std::pair<StringRef, StringRef> split = format.split('\n');
81   os << split.first.trim() << "\n";
82   do {
83     split = split.second.split('\n');
84     StringRef formatChunk = split.first.trim();
85     if (!formatChunk.empty())
86       os.indent(indent) << formatChunk << "\n";
87   } while (!split.second.empty());
88   os << "```\n\n";
89 }
90 
91 static void emitOpTraitsDoc(const Operator &op, raw_ostream &os) {
92   // TODO: We should link to the trait/documentation of it. That also means we
93   // should add descriptions to traits that can be queried.
94   // Collect using set to sort effects, interfaces & traits.
95   std::set<std::string> effects, interfaces, traits;
96   for (auto &trait : op.getTraits()) {
97     if (isa<PredTrait>(&trait))
98       continue;
99 
100     std::string name = trait.getDef().getName().str();
101     StringRef ref = name;
102     StringRef traitName = trait.getDef().getValueAsString("trait");
103     traitName.consume_back("::Trait");
104     traitName.consume_back("::Impl");
105     if (ref.startswith("anonymous_"))
106       name = traitName.str();
107     if (isa<InterfaceTrait>(&trait)) {
108       if (trait.getDef().isSubClassOf("SideEffectsTraitBase")) {
109         auto effectName = trait.getDef().getValueAsString("baseEffectName");
110         effectName.consume_front("::");
111         effectName.consume_front("mlir::");
112         std::string effectStr;
113         llvm::raw_string_ostream os(effectStr);
114         os << effectName << "{";
115         auto list = trait.getDef().getValueAsListOfDefs("effects");
116         llvm::interleaveComma(list, os, [&](Record *rec) {
117           StringRef effect = rec->getValueAsString("effect");
118           effect.consume_front("::");
119           effect.consume_front("mlir::");
120           os << effect << " on " << rec->getValueAsString("resource");
121         });
122         os << "}";
123         effects.insert(os.str());
124         name.append(llvm::formatv(" ({0})", traitName).str());
125       }
126       interfaces.insert(name);
127       continue;
128     }
129 
130     traits.insert(name);
131   }
132   if (!traits.empty()) {
133     llvm::interleaveComma(traits, os << "\nTraits: ");
134     os << "\n";
135   }
136   if (!interfaces.empty()) {
137     llvm::interleaveComma(interfaces, os << "\nInterfaces: ");
138     os << "\n";
139   }
140   if (!effects.empty()) {
141     llvm::interleaveComma(effects, os << "\nEffects: ");
142     os << "\n";
143   }
144 }
145 
146 static void emitOpDoc(const Operator &op, raw_ostream &os) {
147   os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(),
148                       op.getQualCppClassName());
149 
150   // Emit the summary, syntax, and description if present.
151   if (op.hasSummary())
152     os << "\n" << op.getSummary() << "\n\n";
153   if (op.hasAssemblyFormat())
154     emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(),
155                        os);
156   if (op.hasDescription())
157     mlir::tblgen::emitDescription(op.getDescription(), os);
158 
159   emitOpTraitsDoc(op, os);
160 
161   // Emit attributes.
162   if (op.getNumAttributes() != 0) {
163     // TODO: Attributes are only documented by TableGen name, with no further
164     // info. This should be improved.
165     os << "\n#### Attributes:\n\n";
166     os << "| Attribute | MLIR Type | Description |\n"
167        << "| :-------: | :-------: | ----------- |\n";
168     for (const auto &it : op.getAttributes()) {
169       StringRef storageType = it.attr.getStorageType();
170       os << "| `" << it.name << "` | " << storageType << " | "
171          << it.attr.getSummary() << "\n";
172     }
173   }
174 
175   // Emit each of the operands.
176   if (op.getNumOperands() != 0) {
177     os << "\n#### Operands:\n\n";
178     os << "| Operand | Description |\n"
179        << "| :-----: | ----------- |\n";
180     for (const auto &it : op.getOperands())
181       emitNamedConstraint(it, os);
182   }
183 
184   // Emit results.
185   if (op.getNumResults() != 0) {
186     os << "\n#### Results:\n\n";
187     os << "| Result | Description |\n"
188        << "| :----: | ----------- |\n";
189     for (const auto &it : op.getResults())
190       emitNamedConstraint(it, os);
191   }
192 
193   // Emit successors.
194   if (op.getNumSuccessors() != 0) {
195     os << "\n#### Successors:\n\n";
196     os << "| Successor | Description |\n"
197        << "| :-------: | ----------- |\n";
198     for (const auto &it : op.getSuccessors())
199       emitNamedConstraint(it, os);
200   }
201 
202   os << "\n";
203 }
204 
205 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
206   auto opDefs = getRequestedOpDefinitions(recordKeeper);
207 
208   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
209   for (const llvm::Record *opDef : opDefs)
210     emitOpDoc(Operator(opDef), os);
211 }
212 
213 //===----------------------------------------------------------------------===//
214 // Attribute Documentation
215 //===----------------------------------------------------------------------===//
216 
217 static void emitAttrDoc(const Attribute &attr, raw_ostream &os) {
218   os << "### " << attr.getSummary() << "\n\n";
219   emitDescription(attr.getDescription(), os);
220   os << "\n\n";
221 }
222 
223 //===----------------------------------------------------------------------===//
224 // Type Documentation
225 //===----------------------------------------------------------------------===//
226 
227 static void emitTypeDoc(const Type &type, raw_ostream &os) {
228   os << "### " << type.getSummary() << "\n\n";
229   emitDescription(type.getDescription(), os);
230   os << "\n\n";
231 }
232 
233 //===----------------------------------------------------------------------===//
234 // TypeDef Documentation
235 //===----------------------------------------------------------------------===//
236 
237 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def,
238                                             raw_ostream &os) {
239   ArrayRef<AttrOrTypeParameter> parameters = def.getParameters();
240   if (parameters.empty()) {
241     os << "\nSyntax: `!" << def.getDialect().getName() << "."
242        << def.getMnemonic() << "`\n";
243     return;
244   }
245 
246   os << "\nSyntax:\n\n```\n!" << def.getDialect().getName() << "."
247      << def.getMnemonic() << "<\n";
248   for (const auto &it : llvm::enumerate(parameters)) {
249     const AttrOrTypeParameter &param = it.value();
250     os << "  " << param.getSyntax();
251     if (it.index() < (parameters.size() - 1))
252       os << ",";
253     os << "   # " << param.getName() << "\n";
254   }
255   os << ">\n```\n";
256 }
257 
258 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef &def, raw_ostream &os) {
259   os << llvm::formatv("### {0}\n", def.getCppClassName());
260 
261   // Emit the summary if present.
262   if (def.hasSummary())
263     os << "\n" << def.getSummary() << "\n";
264 
265   // Emit the syntax if present.
266   if (def.getMnemonic() && !def.hasCustomAssemblyFormat())
267     emitAttrOrTypeDefAssemblyFormat(def, os);
268 
269   // Emit the description if present.
270   if (def.hasDescription()) {
271     os << "\n";
272     mlir::tblgen::emitDescription(def.getDescription(), os);
273   }
274 
275   // Emit parameter documentation.
276   ArrayRef<AttrOrTypeParameter> parameters = def.getParameters();
277   if (!parameters.empty()) {
278     os << "\n#### Parameters:\n\n";
279     os << "| Parameter | C++ type | Description |\n"
280        << "| :-------: | :-------: | ----------- |\n";
281     for (const auto &it : parameters) {
282       auto desc = it.getSummary();
283       os << "| " << it.getName() << " | `" << it.getCppType() << "` | "
284          << (desc ? *desc : "") << " |\n";
285     }
286   }
287 
288   os << "\n";
289 }
290 
291 static void emitAttrOrTypeDefDoc(const RecordKeeper &recordKeeper,
292                                  raw_ostream &os, StringRef recordTypeName) {
293   std::vector<llvm::Record *> defs =
294       recordKeeper.getAllDerivedDefinitions(recordTypeName);
295 
296   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
297   for (const llvm::Record *def : defs)
298     emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os);
299 }
300 
301 //===----------------------------------------------------------------------===//
302 // Dialect Documentation
303 //===----------------------------------------------------------------------===//
304 
305 static void emitDialectDoc(const Dialect &dialect,
306                            ArrayRef<Attribute> attributes,
307                            ArrayRef<AttrDef> attrDefs, ArrayRef<Operator> ops,
308                            ArrayRef<Type> types, ArrayRef<TypeDef> typeDefs,
309                            raw_ostream &os) {
310   if (selectedDialect.getNumOccurrences() &&
311       dialect.getName() != selectedDialect)
312     return;
313   os << "# '" << dialect.getName() << "' Dialect\n\n";
314   emitIfNotEmpty(dialect.getSummary(), os);
315   emitIfNotEmpty(dialect.getDescription(), os);
316 
317   // Generate a TOC marker except if description already contains one.
318   llvm::Regex r("^[[:space:]]*\\[TOC\\]$", llvm::Regex::RegexFlags::Newline);
319   if (!r.match(dialect.getDescription()))
320     os << "[TOC]\n\n";
321 
322   if (!attributes.empty()) {
323     os << "## Attribute constraint definition\n\n";
324     for (const Attribute &attr : attributes)
325       emitAttrDoc(attr, os);
326   }
327 
328   if (!attrDefs.empty()) {
329     os << "## Attribute definition\n\n";
330     for (const AttrDef &def : attrDefs)
331       emitAttrOrTypeDefDoc(def, os);
332   }
333 
334   // TODO: Add link between use and def for types
335   if (!types.empty()) {
336     os << "## Type constraint definition\n\n";
337     for (const Type &type : types)
338       emitTypeDoc(type, os);
339   }
340 
341   if (!ops.empty()) {
342     os << "## Operation definition\n\n";
343     for (const Operator &op : ops)
344       emitOpDoc(op, os);
345   }
346 
347   if (!typeDefs.empty()) {
348     os << "## Type definition\n\n";
349     for (const TypeDef &def : typeDefs)
350       emitAttrOrTypeDefDoc(def, os);
351   }
352 }
353 
354 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
355   std::vector<Record *> opDefs = getRequestedOpDefinitions(recordKeeper);
356   std::vector<Record *> attrDefs =
357       recordKeeper.getAllDerivedDefinitionsIfDefined("DialectAttr");
358   std::vector<Record *> typeDefs =
359       recordKeeper.getAllDerivedDefinitionsIfDefined("DialectType");
360   std::vector<Record *> typeDefDefs =
361       recordKeeper.getAllDerivedDefinitionsIfDefined("TypeDef");
362   std::vector<Record *> attrDefDefs =
363       recordKeeper.getAllDerivedDefinitionsIfDefined("AttrDef");
364 
365   std::set<Dialect> dialectsWithDocs;
366 
367   llvm::StringMap<std::vector<Attribute>> dialectAttrs;
368   llvm::StringMap<std::vector<AttrDef>> dialectAttrDefs;
369   llvm::StringMap<std::vector<Operator>> dialectOps;
370   llvm::StringMap<std::vector<Type>> dialectTypes;
371   llvm::StringMap<std::vector<TypeDef>> dialectTypeDefs;
372   for (Record *attrDef : attrDefs) {
373     Attribute attr(attrDef);
374     if (const Dialect &dialect = attr.getDialect()) {
375       dialectAttrs[dialect.getName()].push_back(attr);
376       dialectsWithDocs.insert(dialect);
377     }
378   }
379   for (Record *attrDef : attrDefDefs) {
380     AttrDef attr(attrDef);
381     dialectAttrDefs[attr.getDialect().getName()].push_back(attr);
382     dialectsWithDocs.insert(attr.getDialect());
383   }
384   for (Record *opDef : opDefs) {
385     Operator op(opDef);
386     dialectOps[op.getDialect().getName()].push_back(op);
387     dialectsWithDocs.insert(op.getDialect());
388   }
389   for (Record *typeDef : typeDefs) {
390     Type type(typeDef);
391     if (const Dialect &dialect = type.getDialect()) {
392       dialectTypes[dialect.getName()].push_back(type);
393       dialectsWithDocs.insert(dialect);
394     }
395   }
396   for (Record *typeDef : typeDefDefs) {
397     TypeDef type(typeDef);
398     dialectTypeDefs[type.getDialect().getName()].push_back(type);
399     dialectsWithDocs.insert(type.getDialect());
400   }
401 
402   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
403   for (const Dialect &dialect : dialectsWithDocs) {
404     StringRef dialectName = dialect.getName();
405     emitDialectDoc(dialect, dialectAttrs[dialectName],
406                    dialectAttrDefs[dialectName], dialectOps[dialectName],
407                    dialectTypes[dialectName], dialectTypeDefs[dialectName], os);
408   }
409 }
410 
411 //===----------------------------------------------------------------------===//
412 // Gen Registration
413 //===----------------------------------------------------------------------===//
414 
415 static mlir::GenRegistration
416     genAttrRegister("gen-attrdef-doc",
417                     "Generate dialect attribute documentation",
418                     [](const RecordKeeper &records, raw_ostream &os) {
419                       emitAttrOrTypeDefDoc(records, os, "AttrDef");
420                       return false;
421                     });
422 
423 static mlir::GenRegistration
424     genOpRegister("gen-op-doc", "Generate dialect documentation",
425                   [](const RecordKeeper &records, raw_ostream &os) {
426                     emitOpDoc(records, os);
427                     return false;
428                   });
429 
430 static mlir::GenRegistration
431     genTypeRegister("gen-typedef-doc", "Generate dialect type documentation",
432                     [](const RecordKeeper &records, raw_ostream &os) {
433                       emitAttrOrTypeDefDoc(records, os, "TypeDef");
434                       return false;
435                     });
436 
437 static mlir::GenRegistration
438     genRegister("gen-dialect-doc", "Generate dialect documentation",
439                 [](const RecordKeeper &records, raw_ostream &os) {
440                   emitDialectDoc(records, os);
441                   return false;
442                 });
443