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.
emitDescription(StringRef description,raw_ostream & os)46 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) {
47   raw_indented_ostream ros(os);
48   ros.reindent(description.rtrim(" \t"));
49 }
50 
51 // Emits `str` with trailing newline if not empty.
emitIfNotEmpty(StringRef str,raw_ostream & os)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>
emitNamedConstraint(const T & it,raw_ostream & os)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.
emitAssemblyFormat(StringRef opName,StringRef format,raw_ostream & os)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 
emitOpDoc(Operator op,raw_ostream & os)91 static void emitOpDoc(Operator op, raw_ostream &os) {
92   os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(),
93                       op.getQualCppClassName());
94 
95   // Emit the summary, syntax, and description if present.
96   if (op.hasSummary())
97     os << "\n" << op.getSummary() << "\n\n";
98   if (op.hasAssemblyFormat())
99     emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(),
100                        os);
101   if (op.hasDescription())
102     mlir::tblgen::emitDescription(op.getDescription(), os);
103 
104   // Emit attributes.
105   if (op.getNumAttributes() != 0) {
106     // TODO: Attributes are only documented by TableGen name, with no further
107     // info. This should be improved.
108     os << "\n#### Attributes:\n\n";
109     os << "| Attribute | MLIR Type | Description |\n"
110        << "| :-------: | :-------: | ----------- |\n";
111     for (const auto &it : op.getAttributes()) {
112       StringRef storageType = it.attr.getStorageType();
113       os << "`" << it.name << "` | " << storageType << " | "
114          << it.attr.getSummary() << "\n";
115     }
116   }
117 
118   // Emit each of the operands.
119   if (op.getNumOperands() != 0) {
120     os << "\n#### Operands:\n\n";
121     os << "| Operand | Description |\n"
122        << "| :-----: | ----------- |\n";
123     for (const auto &it : op.getOperands())
124       emitNamedConstraint(it, os);
125   }
126 
127   // Emit results.
128   if (op.getNumResults() != 0) {
129     os << "\n#### Results:\n\n";
130     os << "| Result | Description |\n"
131        << "| :----: | ----------- |\n";
132     for (const auto &it : op.getResults())
133       emitNamedConstraint(it, os);
134   }
135 
136   // Emit successors.
137   if (op.getNumSuccessors() != 0) {
138     os << "\n#### Successors:\n\n";
139     os << "| Successor | Description |\n"
140        << "| :-------: | ----------- |\n";
141     for (const auto &it : op.getSuccessors())
142       emitNamedConstraint(it, os);
143   }
144 
145   os << "\n";
146 }
147 
emitOpDoc(const RecordKeeper & recordKeeper,raw_ostream & os)148 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
149   auto opDefs = getRequestedOpDefinitions(recordKeeper);
150 
151   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
152   for (const llvm::Record *opDef : opDefs)
153     emitOpDoc(Operator(opDef), os);
154 }
155 
156 //===----------------------------------------------------------------------===//
157 // Type Documentation
158 //===----------------------------------------------------------------------===//
159 
emitTypeDoc(const Type & type,raw_ostream & os)160 static void emitTypeDoc(const Type &type, raw_ostream &os) {
161   os << "### " << type.getSummary() << "\n";
162   emitDescription(type.getDescription(), os);
163   os << "\n";
164 }
165 
166 //===----------------------------------------------------------------------===//
167 // TypeDef Documentation
168 //===----------------------------------------------------------------------===//
169 
emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef & def,raw_ostream & os)170 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def,
171                                             raw_ostream &os) {
172   SmallVector<AttrOrTypeParameter, 4> parameters;
173   def.getParameters(parameters);
174   if (parameters.empty()) {
175     os << "\nSyntax: `!" << def.getDialect().getName() << "."
176        << def.getMnemonic() << "`\n";
177     return;
178   }
179 
180   os << "\nSyntax:\n\n```\n!" << def.getDialect().getName() << "."
181      << def.getMnemonic() << "<\n";
182   for (auto it : llvm::enumerate(parameters)) {
183     const AttrOrTypeParameter &param = it.value();
184     os << "  " << param.getSyntax();
185     if (it.index() < (parameters.size() - 1))
186       os << ",";
187     os << "   # " << param.getName() << "\n";
188   }
189   os << ">\n```\n";
190 }
191 
emitAttrOrTypeDefDoc(const AttrOrTypeDef & def,raw_ostream & os)192 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef &def, raw_ostream &os) {
193   os << llvm::formatv("### {0}\n", def.getCppClassName());
194 
195   // Emit the summary if present.
196   if (def.hasSummary())
197     os << "\n" << def.getSummary() << "\n";
198 
199   // Emit the syntax if present.
200   if (def.getMnemonic() && def.getPrinterCode() == StringRef() &&
201       def.getParserCode() == StringRef())
202     emitAttrOrTypeDefAssemblyFormat(def, os);
203 
204   // Emit the description if present.
205   if (def.hasDescription()) {
206     os << "\n";
207     mlir::tblgen::emitDescription(def.getDescription(), os);
208   }
209 
210   // Emit parameter documentation.
211   SmallVector<AttrOrTypeParameter, 4> parameters;
212   def.getParameters(parameters);
213   if (!parameters.empty()) {
214     os << "\n#### Parameters:\n\n";
215     os << "| Parameter | C++ type | Description |\n"
216        << "| :-------: | :-------: | ----------- |\n";
217     for (const auto &it : parameters) {
218       auto desc = it.getSummary();
219       os << "| " << it.getName() << " | `" << it.getCppType() << "` | "
220          << (desc ? *desc : "") << " |\n";
221     }
222   }
223 
224   os << "\n";
225 }
226 
emitAttrOrTypeDefDoc(const RecordKeeper & recordKeeper,raw_ostream & os,StringRef recordTypeName)227 static void emitAttrOrTypeDefDoc(const RecordKeeper &recordKeeper,
228                                  raw_ostream &os, StringRef recordTypeName) {
229   std::vector<llvm::Record *> defs =
230       recordKeeper.getAllDerivedDefinitions(recordTypeName);
231 
232   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
233   for (const llvm::Record *def : defs)
234     emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os);
235 }
236 
237 //===----------------------------------------------------------------------===//
238 // Dialect Documentation
239 //===----------------------------------------------------------------------===//
240 
emitDialectDoc(const Dialect & dialect,ArrayRef<AttrDef> attrDefs,ArrayRef<Operator> ops,ArrayRef<Type> types,ArrayRef<TypeDef> typeDefs,raw_ostream & os)241 static void emitDialectDoc(const Dialect &dialect, ArrayRef<AttrDef> attrDefs,
242                            ArrayRef<Operator> ops, ArrayRef<Type> types,
243                            ArrayRef<TypeDef> typeDefs, raw_ostream &os) {
244   if (selectedDialect.getNumOccurrences() &&
245       dialect.getName() != selectedDialect)
246     return;
247   os << "# '" << dialect.getName() << "' Dialect\n\n";
248   emitIfNotEmpty(dialect.getSummary(), os);
249   emitIfNotEmpty(dialect.getDescription(), os);
250 
251   // Generate a TOC marker except if description already contains one.
252   llvm::Regex r("^[[:space:]]*\\[TOC\\]$", llvm::Regex::RegexFlags::Newline);
253   if (!r.match(dialect.getDescription()))
254     os << "[TOC]\n\n";
255 
256   if (!attrDefs.empty()) {
257     os << "## Attribute definition\n\n";
258     for (const AttrDef &def : attrDefs)
259       emitAttrOrTypeDefDoc(def, os);
260   }
261 
262   // TODO: Add link between use and def for types
263   if (!types.empty()) {
264     os << "## Type constraint definition\n\n";
265     for (const Type &type : types)
266       emitTypeDoc(type, os);
267   }
268 
269   if (!ops.empty()) {
270     os << "## Operation definition\n\n";
271     for (const Operator &op : ops)
272       emitOpDoc(op, os);
273   }
274 
275   if (!typeDefs.empty()) {
276     os << "## Type definition\n\n";
277     for (const TypeDef &def : typeDefs)
278       emitAttrOrTypeDefDoc(def, os);
279   }
280 }
281 
emitDialectDoc(const RecordKeeper & recordKeeper,raw_ostream & os)282 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
283   std::vector<Record *> opDefs = getRequestedOpDefinitions(recordKeeper);
284   std::vector<Record *> typeDefs =
285       recordKeeper.getAllDerivedDefinitions("DialectType");
286   std::vector<Record *> typeDefDefs =
287       recordKeeper.getAllDerivedDefinitions("TypeDef");
288   std::vector<Record *> attrDefDefs =
289       recordKeeper.getAllDerivedDefinitions("AttrDef");
290 
291   std::set<Dialect> dialectsWithDocs;
292 
293   llvm::StringMap<std::vector<AttrDef>> dialectAttrDefs;
294   llvm::StringMap<std::vector<Operator>> dialectOps;
295   llvm::StringMap<std::vector<Type>> dialectTypes;
296   llvm::StringMap<std::vector<TypeDef>> dialectTypeDefs;
297   for (auto *attrDef : attrDefDefs) {
298     AttrDef attr(attrDef);
299     dialectAttrDefs[attr.getDialect().getName()].push_back(attr);
300     dialectsWithDocs.insert(attr.getDialect());
301   }
302   for (auto *opDef : opDefs) {
303     Operator op(opDef);
304     dialectOps[op.getDialect().getName()].push_back(op);
305     dialectsWithDocs.insert(op.getDialect());
306   }
307   for (auto *typeDef : typeDefs) {
308     Type type(typeDef);
309     if (auto dialect = type.getDialect())
310       dialectTypes[dialect.getName()].push_back(type);
311   }
312   for (auto *typeDef : typeDefDefs) {
313     TypeDef type(typeDef);
314     dialectTypeDefs[type.getDialect().getName()].push_back(type);
315     dialectsWithDocs.insert(type.getDialect());
316   }
317 
318   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
319   for (const Dialect &dialect : dialectsWithDocs) {
320     StringRef dialectName = dialect.getName();
321     emitDialectDoc(dialect, dialectAttrDefs[dialectName],
322                    dialectOps[dialectName], dialectTypes[dialectName],
323                    dialectTypeDefs[dialectName], os);
324   }
325 }
326 
327 //===----------------------------------------------------------------------===//
328 // Gen Registration
329 //===----------------------------------------------------------------------===//
330 
331 static mlir::GenRegistration
332     genAttrRegister("gen-attrdef-doc",
333                     "Generate dialect attribute documentation",
__anon970f64570102(const RecordKeeper &records, raw_ostream &os) 334                     [](const RecordKeeper &records, raw_ostream &os) {
335                       emitAttrOrTypeDefDoc(records, os, "AttrDef");
336                       return false;
337                     });
338 
339 static mlir::GenRegistration
340     genOpRegister("gen-op-doc", "Generate dialect documentation",
__anon970f64570202(const RecordKeeper &records, raw_ostream &os) 341                   [](const RecordKeeper &records, raw_ostream &os) {
342                     emitOpDoc(records, os);
343                     return false;
344                   });
345 
346 static mlir::GenRegistration
347     genTypeRegister("gen-typedef-doc", "Generate dialect type documentation",
__anon970f64570302(const RecordKeeper &records, raw_ostream &os) 348                     [](const RecordKeeper &records, raw_ostream &os) {
349                       emitAttrOrTypeDefDoc(records, os, "TypeDef");
350                       return false;
351                     });
352 
353 static mlir::GenRegistration
354     genRegister("gen-dialect-doc", "Generate dialect documentation",
__anon970f64570402(const RecordKeeper &records, raw_ostream &os) 355                 [](const RecordKeeper &records, raw_ostream &os) {
356                   emitDialectDoc(records, os);
357                   return false;
358                 });
359