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