xref: /llvm-project/mlir/tools/mlir-tblgen/OpDocGen.cpp (revision caddfbd2a94c7014173ce891fc0233d58b3c9db8)
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 "mlir/Support/IndentedOstream.h"
16 #include "mlir/TableGen/AttrOrTypeDef.h"
17 #include "mlir/TableGen/GenInfo.h"
18 #include "mlir/TableGen/Operator.h"
19 #include "llvm/ADT/DenseMap.h"
20 #include "llvm/ADT/StringExtras.h"
21 #include "llvm/Support/FormatVariadic.h"
22 #include "llvm/Support/Signals.h"
23 #include "llvm/TableGen/Error.h"
24 #include "llvm/TableGen/Record.h"
25 #include "llvm/TableGen/TableGenBackend.h"
26 
27 #include <set>
28 
29 using namespace llvm;
30 using namespace mlir;
31 using namespace mlir::tblgen;
32 
33 using mlir::tblgen::Operator;
34 
35 // Emit the description by aligning the text to the left per line (e.g.,
36 // removing the minimum indentation across the block).
37 //
38 // This expects that the description in the tablegen file is already formatted
39 // in a way the user wanted but has some additional indenting due to being
40 // nested in the op definition.
41 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) {
42   raw_indented_ostream ros(os);
43   ros.reindent(description.rtrim(" \t"));
44 }
45 
46 // Emits `str` with trailing newline if not empty.
47 static void emitIfNotEmpty(StringRef str, raw_ostream &os) {
48   if (!str.empty()) {
49     emitDescription(str, os);
50     os << "\n";
51   }
52 }
53 
54 /// Emit the given named constraint.
55 template <typename T>
56 static void emitNamedConstraint(const T &it, raw_ostream &os) {
57   if (!it.name.empty())
58     os << "`" << it.name << "`";
59   else
60     os << "&laquo;unnamed&raquo;";
61   os << " | " << it.constraint.getSummary() << "\n";
62 }
63 
64 //===----------------------------------------------------------------------===//
65 // Operation Documentation
66 //===----------------------------------------------------------------------===//
67 
68 /// Emit the assembly format of an operation.
69 static void emitAssemblyFormat(StringRef opName, StringRef format,
70                                raw_ostream &os) {
71   os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` ";
72 
73   // Print the assembly format aligned.
74   unsigned indent = strlen("operation ::= ");
75   std::pair<StringRef, StringRef> split = format.split('\n');
76   os << split.first.trim() << "\n";
77   do {
78     split = split.second.split('\n');
79     StringRef formatChunk = split.first.trim();
80     if (!formatChunk.empty())
81       os.indent(indent) << formatChunk << "\n";
82   } while (!split.second.empty());
83   os << "```\n\n";
84 }
85 
86 static void emitOpDoc(Operator op, raw_ostream &os) {
87   os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(),
88                       op.getQualCppClassName());
89 
90   // Emit the summary, syntax, and description if present.
91   if (op.hasSummary())
92     os << "\n" << op.getSummary() << "\n\n";
93   if (op.hasAssemblyFormat())
94     emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(),
95                        os);
96   if (op.hasDescription())
97     mlir::tblgen::emitDescription(op.getDescription(), os);
98 
99   // Emit attributes.
100   if (op.getNumAttributes() != 0) {
101     // TODO: Attributes are only documented by TableGen name, with no further
102     // info. This should be improved.
103     os << "\n#### Attributes:\n\n";
104     os << "| Attribute | MLIR Type | Description |\n"
105        << "| :-------: | :-------: | ----------- |\n";
106     for (const auto &it : op.getAttributes()) {
107       StringRef storageType = it.attr.getStorageType();
108       os << "`" << it.name << "` | " << storageType << " | "
109          << it.attr.getSummary() << "\n";
110     }
111   }
112 
113   // Emit each of the operands.
114   if (op.getNumOperands() != 0) {
115     os << "\n#### Operands:\n\n";
116     os << "| Operand | Description |\n"
117        << "| :-----: | ----------- |\n";
118     for (const auto &it : op.getOperands())
119       emitNamedConstraint(it, os);
120   }
121 
122   // Emit results.
123   if (op.getNumResults() != 0) {
124     os << "\n#### Results:\n\n";
125     os << "| Result | Description |\n"
126        << "| :----: | ----------- |\n";
127     for (const auto &it : op.getResults())
128       emitNamedConstraint(it, os);
129   }
130 
131   // Emit successors.
132   if (op.getNumSuccessors() != 0) {
133     os << "\n#### Successors:\n\n";
134     os << "| Successor | Description |\n"
135        << "| :-------: | ----------- |\n";
136     for (const auto &it : op.getSuccessors())
137       emitNamedConstraint(it, os);
138   }
139 
140   os << "\n";
141 }
142 
143 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
144   auto opDefs = recordKeeper.getAllDerivedDefinitions("Op");
145 
146   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
147   for (const llvm::Record *opDef : opDefs)
148     emitOpDoc(Operator(opDef), os);
149 }
150 
151 //===----------------------------------------------------------------------===//
152 // Type Documentation
153 //===----------------------------------------------------------------------===//
154 
155 static void emitTypeDoc(const Type &type, raw_ostream &os) {
156   os << "### " << type.getSummary() << "\n";
157   emitDescription(type.getDescription(), os);
158   os << "\n";
159 }
160 
161 //===----------------------------------------------------------------------===//
162 // TypeDef Documentation
163 //===----------------------------------------------------------------------===//
164 
165 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def,
166                                             raw_ostream &os) {
167   SmallVector<AttrOrTypeParameter, 4> parameters;
168   def.getParameters(parameters);
169   if (parameters.empty()) {
170     os << "\nSyntax: `!" << def.getDialect().getName() << "."
171        << def.getMnemonic() << "`\n";
172     return;
173   }
174 
175   os << "\nSyntax:\n\n```\n!" << def.getDialect().getName() << "."
176      << def.getMnemonic() << "<\n";
177   for (auto it : llvm::enumerate(parameters)) {
178     const AttrOrTypeParameter &param = it.value();
179     os << "  " << param.getSyntax();
180     if (it.index() < (parameters.size() - 1))
181       os << ",";
182     os << "   # " << param.getName() << "\n";
183   }
184   os << ">\n```\n";
185 }
186 
187 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef &def, raw_ostream &os) {
188   os << llvm::formatv("### {0}\n", def.getCppClassName());
189 
190   // Emit the summary if present.
191   if (def.hasSummary())
192     os << "\n" << def.getSummary() << "\n";
193 
194   // Emit the syntax if present.
195   if (def.getMnemonic() && def.getPrinterCode() == StringRef() &&
196       def.getParserCode() == StringRef())
197     emitAttrOrTypeDefAssemblyFormat(def, os);
198 
199   // Emit the description if present.
200   if (def.hasDescription()) {
201     os << "\n";
202     mlir::tblgen::emitDescription(def.getDescription(), os);
203   }
204 
205   // Emit parameter documentation.
206   SmallVector<AttrOrTypeParameter, 4> parameters;
207   def.getParameters(parameters);
208   if (!parameters.empty()) {
209     os << "\n#### Parameters:\n\n";
210     os << "| Parameter | C++ type | Description |\n"
211        << "| :-------: | :-------: | ----------- |\n";
212     for (const auto &it : parameters) {
213       auto desc = it.getSummary();
214       os << "| " << it.getName() << " | `" << it.getCppType() << "` | "
215          << (desc ? *desc : "") << " |\n";
216     }
217   }
218 
219   os << "\n";
220 }
221 
222 static void emitAttrOrTypeDefDoc(const RecordKeeper &recordKeeper,
223                                  raw_ostream &os, StringRef recordTypeName) {
224   std::vector<llvm::Record *> defs =
225       recordKeeper.getAllDerivedDefinitions(recordTypeName);
226 
227   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
228   for (const llvm::Record *def : defs)
229     emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os);
230 }
231 
232 //===----------------------------------------------------------------------===//
233 // Dialect Documentation
234 //===----------------------------------------------------------------------===//
235 
236 static void emitDialectDoc(const Dialect &dialect, ArrayRef<AttrDef> attrDefs,
237                            ArrayRef<Operator> ops, ArrayRef<Type> types,
238                            ArrayRef<TypeDef> typeDefs, raw_ostream &os) {
239   os << "# '" << dialect.getName() << "' Dialect\n\n";
240   emitIfNotEmpty(dialect.getSummary(), os);
241   emitIfNotEmpty(dialect.getDescription(), os);
242 
243   os << "[TOC]\n\n";
244 
245   if (!attrDefs.empty()) {
246     os << "## Attribute definition\n\n";
247     for (const AttrDef &def : attrDefs)
248       emitAttrOrTypeDefDoc(def, os);
249   }
250 
251   // TODO: Add link between use and def for types
252   if (!types.empty()) {
253     os << "## Type constraint definition\n\n";
254     for (const Type &type : types)
255       emitTypeDoc(type, os);
256   }
257 
258   if (!ops.empty()) {
259     os << "## Operation definition\n\n";
260     for (const Operator &op : ops)
261       emitOpDoc(op, os);
262   }
263 
264   if (!typeDefs.empty()) {
265     os << "## Type definition\n\n";
266     for (const TypeDef &def : typeDefs)
267       emitAttrOrTypeDefDoc(def, os);
268   }
269 }
270 
271 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
272   std::vector<Record *> opDefs = recordKeeper.getAllDerivedDefinitions("Op");
273   std::vector<Record *> typeDefs =
274       recordKeeper.getAllDerivedDefinitions("DialectType");
275   std::vector<Record *> typeDefDefs =
276       recordKeeper.getAllDerivedDefinitions("TypeDef");
277   std::vector<Record *> attrDefDefs =
278       recordKeeper.getAllDerivedDefinitions("AttrDef");
279 
280   std::set<Dialect> dialectsWithDocs;
281 
282   llvm::StringMap<std::vector<AttrDef>> dialectAttrDefs;
283   llvm::StringMap<std::vector<Operator>> dialectOps;
284   llvm::StringMap<std::vector<Type>> dialectTypes;
285   llvm::StringMap<std::vector<TypeDef>> dialectTypeDefs;
286   for (auto *attrDef : attrDefDefs) {
287     AttrDef attr(attrDef);
288     dialectAttrDefs[attr.getDialect().getName()].push_back(attr);
289     dialectsWithDocs.insert(attr.getDialect());
290   }
291   for (auto *opDef : opDefs) {
292     Operator op(opDef);
293     dialectOps[op.getDialect().getName()].push_back(op);
294     dialectsWithDocs.insert(op.getDialect());
295   }
296   for (auto *typeDef : typeDefs) {
297     Type type(typeDef);
298     if (auto dialect = type.getDialect())
299       dialectTypes[dialect.getName()].push_back(type);
300   }
301   for (auto *typeDef : typeDefDefs) {
302     TypeDef type(typeDef);
303     dialectTypeDefs[type.getDialect().getName()].push_back(type);
304     dialectsWithDocs.insert(type.getDialect());
305   }
306 
307   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
308   for (const Dialect &dialect : dialectsWithDocs) {
309     StringRef dialectName = dialect.getName();
310     emitDialectDoc(dialect, dialectAttrDefs[dialectName],
311                    dialectOps[dialectName], dialectTypes[dialectName],
312                    dialectTypeDefs[dialectName], os);
313   }
314 }
315 
316 //===----------------------------------------------------------------------===//
317 // Gen Registration
318 //===----------------------------------------------------------------------===//
319 
320 static mlir::GenRegistration
321     genAttrRegister("gen-attrdef-doc",
322                     "Generate dialect attribute documentation",
323                     [](const RecordKeeper &records, raw_ostream &os) {
324                       emitAttrOrTypeDefDoc(records, os, "AttrDef");
325                       return false;
326                     });
327 
328 static mlir::GenRegistration
329     genOpRegister("gen-op-doc", "Generate dialect documentation",
330                   [](const RecordKeeper &records, raw_ostream &os) {
331                     emitOpDoc(records, os);
332                     return false;
333                   });
334 
335 static mlir::GenRegistration
336     genTypeRegister("gen-typedef-doc", "Generate dialect type documentation",
337                     [](const RecordKeeper &records, raw_ostream &os) {
338                       emitAttrOrTypeDefDoc(records, os, "TypeDef");
339                       return false;
340                     });
341 
342 static mlir::GenRegistration
343     genRegister("gen-dialect-doc", "Generate dialect documentation",
344                 [](const RecordKeeper &records, raw_ostream &os) {
345                   emitDialectDoc(records, os);
346                   return false;
347                 });
348