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