xref: /llvm-project/mlir/tools/mlir-tblgen/OpDocGen.cpp (revision be185b6a7355fdfeb1c31df2e1272366fe58b01f)
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/TableGen/GenInfo.h"
16 #include "mlir/TableGen/Operator.h"
17 #include "llvm/ADT/DenseMap.h"
18 #include "llvm/ADT/StringExtras.h"
19 #include "llvm/Support/FormatVariadic.h"
20 #include "llvm/Support/Signals.h"
21 #include "llvm/TableGen/Error.h"
22 #include "llvm/TableGen/Record.h"
23 #include "llvm/TableGen/TableGenBackend.h"
24 
25 using namespace llvm;
26 using namespace mlir;
27 using namespace mlir::tblgen;
28 
29 using mlir::tblgen::Operator;
30 
31 // Emit the description by aligning the text to the left per line (e.g.,
32 // removing the minimum indentation across the block).
33 //
34 // This expects that the description in the tablegen file is already formatted
35 // in a way the user wanted but has some additional indenting due to being
36 // nested in the op definition.
37 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) {
38   // Determine the minimum number of spaces in a line.
39   size_t min_indent = -1;
40   StringRef remaining = description;
41   while (!remaining.empty()) {
42     auto split = remaining.split('\n');
43     size_t indent = split.first.find_first_not_of(" \t");
44     if (indent != StringRef::npos)
45       min_indent = std::min(indent, min_indent);
46     remaining = split.second;
47   }
48 
49   // Print out the description indented.
50   os << "\n";
51   remaining = description;
52   bool printed = false;
53   while (!remaining.empty()) {
54     auto split = remaining.split('\n');
55     if (split.second.empty()) {
56       // Skip last line with just spaces.
57       if (split.first.ltrim().empty())
58         break;
59     }
60     // Print empty new line without spaces if line only has spaces, unless no
61     // text has been emitted before.
62     if (split.first.ltrim().empty()) {
63       if (printed)
64         os << "\n";
65     } else {
66       os << split.first.substr(min_indent) << "\n";
67       printed = true;
68     }
69     remaining = split.second;
70   }
71 }
72 
73 // Emits `str` with trailing newline if not empty.
74 static void emitIfNotEmpty(StringRef str, raw_ostream &os) {
75   if (!str.empty()) {
76     emitDescription(str, os);
77     os << "\n";
78   }
79 }
80 
81 /// Emit the given named constraint.
82 template <typename T>
83 static void emitNamedConstraint(const T &it, raw_ostream &os) {
84   if (!it.name.empty())
85     os << "`" << it.name << "`";
86   else
87     os << "&laquo;unnamed&raquo;";
88   os << " | " << it.constraint.getDescription() << "\n";
89 }
90 
91 //===----------------------------------------------------------------------===//
92 // Operation Documentation
93 //===----------------------------------------------------------------------===//
94 
95 /// Emit the assembly format of an operation.
96 static void emitAssemblyFormat(StringRef opName, StringRef format,
97                                raw_ostream &os) {
98   os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` ";
99 
100   // Print the assembly format aligned.
101   unsigned indent = strlen("operation ::= ");
102   std::pair<StringRef, StringRef> split = format.split('\n');
103   os << split.first.trim() << "\n";
104   do {
105     split = split.second.split('\n');
106     StringRef formatChunk = split.first.trim();
107     if (!formatChunk.empty())
108       os.indent(indent) << formatChunk << "\n";
109   } while (!split.second.empty());
110   os << "```\n\n";
111 }
112 
113 static void emitOpDoc(Operator op, raw_ostream &os) {
114   os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(),
115                       op.getQualCppClassName());
116 
117   // Emit the summary, syntax, and description if present.
118   if (op.hasSummary())
119     os << "\n" << op.getSummary() << "\n";
120   if (op.hasAssemblyFormat())
121     emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(),
122                        os);
123   if (op.hasDescription())
124     mlir::tblgen::emitDescription(op.getDescription(), os);
125 
126   // Emit attributes.
127   if (op.getNumAttributes() != 0) {
128     // TODO: Attributes are only documented by TableGen name, with no further
129     // info. This should be improved.
130     os << "\n#### Attributes:\n\n";
131     os << "| Attribute | MLIR Type | Description |\n"
132        << "| :-------: | :-------: | ----------- |\n";
133     for (const auto &it : op.getAttributes()) {
134       StringRef storageType = it.attr.getStorageType();
135       os << "`" << it.name << "` | " << storageType << " | "
136          << it.attr.getDescription() << "\n";
137     }
138   }
139 
140   // Emit each of the operands.
141   if (op.getNumOperands() != 0) {
142     os << "\n#### Operands:\n\n";
143     os << "| Operand | Description |\n"
144        << "| :-----: | ----------- |\n";
145     for (const auto &it : op.getOperands())
146       emitNamedConstraint(it, os);
147   }
148 
149   // Emit results.
150   if (op.getNumResults() != 0) {
151     os << "\n#### Results:\n\n";
152     os << "| Result | Description |\n"
153        << "| :----: | ----------- |\n";
154     for (const auto &it : op.getResults())
155       emitNamedConstraint(it, os);
156   }
157 
158   // Emit successors.
159   if (op.getNumSuccessors() != 0) {
160     os << "\n#### Successors:\n\n";
161     os << "| Successor | Description |\n"
162        << "| :-------: | ----------- |\n";
163     for (const auto &it : op.getSuccessors())
164       emitNamedConstraint(it, os);
165   }
166 
167   os << "\n";
168 }
169 
170 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
171   auto opDefs = recordKeeper.getAllDerivedDefinitions("Op");
172 
173   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
174   for (const llvm::Record *opDef : opDefs)
175     emitOpDoc(Operator(opDef), os);
176 }
177 
178 //===----------------------------------------------------------------------===//
179 // Type Documentation
180 //===----------------------------------------------------------------------===//
181 
182 static void emitTypeDoc(const Type &type, raw_ostream &os) {
183   os << "### " << type.getDescription() << "\n";
184   emitDescription(type.getTypeDescription(), os);
185   os << "\n";
186 }
187 
188 //===----------------------------------------------------------------------===//
189 // Dialect Documentation
190 //===----------------------------------------------------------------------===//
191 
192 static void emitDialectDoc(const Dialect &dialect, ArrayRef<Operator> ops,
193                            ArrayRef<Type> types, raw_ostream &os) {
194   os << "# '" << dialect.getName() << "' Dialect\n\n";
195   emitIfNotEmpty(dialect.getSummary(), os);
196   emitIfNotEmpty(dialect.getDescription(), os);
197 
198   os << "[TOC]\n\n";
199 
200   // TODO: Add link between use and def for types
201   if (!types.empty()) {
202     os << "## Type definition\n\n";
203     for (const Type &type : types)
204       emitTypeDoc(type, os);
205   }
206 
207   if (!ops.empty()) {
208     os << "## Operation definition\n\n";
209     for (const Operator &op : ops)
210       emitOpDoc(op, os);
211   }
212 }
213 
214 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
215   const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op");
216   const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType");
217 
218   std::map<Dialect, std::vector<Operator>> dialectOps;
219   std::map<Dialect, std::vector<Type>> dialectTypes;
220   for (auto *opDef : opDefs) {
221     Operator op(opDef);
222     dialectOps[op.getDialect()].push_back(op);
223   }
224   for (auto *typeDef : typeDefs) {
225     Type type(typeDef);
226     if (auto dialect = type.getDialect())
227       dialectTypes[dialect].push_back(type);
228   }
229 
230   os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
231   for (auto dialectWithOps : dialectOps)
232     emitDialectDoc(dialectWithOps.first, dialectWithOps.second,
233                    dialectTypes[dialectWithOps.first], os);
234 }
235 
236 //===----------------------------------------------------------------------===//
237 // Gen Registration
238 //===----------------------------------------------------------------------===//
239 
240 static mlir::GenRegistration
241     genOpRegister("gen-op-doc", "Generate dialect documentation",
242                   [](const RecordKeeper &records, raw_ostream &os) {
243                     emitOpDoc(records, os);
244                     return false;
245                   });
246 
247 static mlir::GenRegistration
248     genRegister("gen-dialect-doc", "Generate dialect documentation",
249                 [](const RecordKeeper &records, raw_ostream &os) {
250                   emitDialectDoc(records, os);
251                   return false;
252                 });
253