1 //===- OpDocGen.cpp - MLIR operation documentation generator --------------===//
2 //
3 // Part of the MLIR 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.
emitDescription(StringRef description,raw_ostream & os)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.
emitIfNotEmpty(StringRef str,raw_ostream & os)74 static void emitIfNotEmpty(StringRef str, raw_ostream &os) {
75 if (!str.empty()) {
76 emitDescription(str, os);
77 os << "\n";
78 }
79 }
80
emitOpDocForDialect(const Dialect & dialect,const std::vector<Operator> & ops,const std::vector<Type> & types,raw_ostream & os)81 static void emitOpDocForDialect(const Dialect &dialect,
82 const std::vector<Operator> &ops,
83 const std::vector<Type> &types,
84 raw_ostream &os) {
85 os << "# Dialect '" << dialect.getName() << "' definition\n\n";
86 emitIfNotEmpty(dialect.getSummary(), os);
87 emitIfNotEmpty(dialect.getDescription(), os);
88
89 // TODO(b/143543720) Generate TOC where extension is not supported.
90 os << "[TOC]\n\n";
91
92 // TODO(antiagainst): Add link between use and def for types
93 if (!types.empty())
94 os << "## Type definition\n\n";
95 for (auto type : types) {
96 os << "### " << type.getDescription() << "\n";
97 emitDescription(type.getTypeDescription(), os);
98 os << "\n";
99 }
100
101 if (!ops.empty())
102 os << "## Operation definition\n\n";
103 for (auto op : ops) {
104 os << "### " << op.getOperationName() << " (" << op.getQualCppClassName()
105 << ")";
106
107 // Emit summary & description of operator.
108 if (op.hasSummary())
109 os << "\n" << op.getSummary() << "\n";
110 os << "\n#### Description:\n\n";
111 if (op.hasDescription())
112 mlir::tblgen::emitDescription(op.getDescription(), os);
113
114 // Emit operands & type of operand. All operands are numbered, some may be
115 // named too.
116 os << "\n#### Operands:\n\n";
117 for (const auto &operand : op.getOperands()) {
118 os << "1. ";
119 if (!operand.name.empty())
120 os << "`" << operand.name << "`: ";
121 else
122 os << "«unnamed»: ";
123 os << operand.constraint.getDescription() << "\n";
124 }
125
126 // Emit attributes.
127 // TODO: Attributes are only documented by TableGen name, with no further
128 // info. This should be improved.
129 os << "\n#### Attributes:\n\n";
130 if (op.getNumAttributes() > 0) {
131 os << "| Attribute | MLIR Type | Description |\n"
132 << "| :-------: | :-------: | ----------- |\n";
133 }
134 for (auto namedAttr : op.getAttributes()) {
135 os << "| `" << namedAttr.name << "` | `"
136 << namedAttr.attr.getStorageType() << "` | "
137 << namedAttr.attr.getDescription() << " attribute |\n";
138 }
139
140 // Emit results.
141 os << "\n#### Results:\n\n";
142 for (unsigned i = 0, e = op.getNumResults(); i < e; ++i) {
143 os << "1. ";
144 auto name = op.getResultName(i);
145 if (name.empty())
146 os << "«unnamed»: ";
147 else
148 os << "`" << name << "`: ";
149 os << op.getResultTypeConstraint(i).getDescription() << "\n";
150 }
151
152 os << "\n";
153 }
154 }
155
emitOpDoc(const RecordKeeper & recordKeeper,raw_ostream & os)156 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) {
157 const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op");
158 const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType");
159
160 std::map<Dialect, std::vector<Operator>> dialectOps;
161 std::map<Dialect, std::vector<Type>> dialectTypes;
162 for (auto *opDef : opDefs) {
163 Operator op(opDef);
164 dialectOps[op.getDialect()].push_back(op);
165 }
166 for (auto *typeDef : typeDefs) {
167 Type type(typeDef);
168 if (auto dialect = type.getDialect())
169 dialectTypes[dialect].push_back(type);
170 }
171
172 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
173 for (auto dialectWithOps : dialectOps)
174 emitOpDocForDialect(dialectWithOps.first, dialectWithOps.second,
175 dialectTypes[dialectWithOps.first], os);
176 }
177
178 static mlir::GenRegistration
179 genRegister("gen-op-doc", "Generate operation documentation",
__anon3bf91ac70102(const RecordKeeper &records, raw_ostream &os) 180 [](const RecordKeeper &records, raw_ostream &os) {
181 emitOpDoc(records, os);
182 return false;
183 });
184