1 //===- OpDocGen.cpp - MLIR operation documentation generator --------------===//
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
7 //===----------------------------------------------------------------------===//
9 // OpDocGen uses the description of operations to generate documentation for the
12 //===----------------------------------------------------------------------===//
14 #include "DialectGenUtilities.h"
15 #include "DocGenUtilities.h"
16 #include "OpGenHelpers.h"
17 #include "mlir/Support/IndentedOstream.h"
18 #include "mlir/TableGen/AttrOrTypeDef.h"
19 #include "mlir/TableGen/GenInfo.h"
20 #include "mlir/TableGen/Operator.h"
21 #include "llvm/ADT/DenseMap.h"
22 #include "llvm/ADT/SetVector.h"
23 #include "llvm/ADT/StringExtras.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/CommandLine.h"
26 #include "llvm/Support/FormatVariadic.h"
27 #include "llvm/Support/Regex.h"
28 #include "llvm/Support/Signals.h"
29 #include "llvm/TableGen/Error.h"
30 #include "llvm/TableGen/Record.h"
31 #include "llvm/TableGen/TableGenBackend.h"
36 //===----------------------------------------------------------------------===//
37 // Commandline Options
38 //===----------------------------------------------------------------------===//
39 static llvm::cl::OptionCategory
40 docCat("Options for -gen-(attrdef|typedef|op|dialect)-doc");
41 llvm::cl::opt
<std::string
>
42 stripPrefix("strip-prefix",
43 llvm::cl::desc("Strip prefix of the fully qualified names"),
44 llvm::cl::init("::mlir::"), llvm::cl::cat(docCat
));
45 llvm::cl::opt
<bool> allowHugoSpecificFeatures(
46 "allow-hugo-specific-features",
47 llvm::cl::desc("Allows using features specific to Hugo"),
48 llvm::cl::init(false), llvm::cl::cat(docCat
));
52 using namespace mlir::tblgen
;
53 using mlir::tblgen::Operator
;
55 void mlir::tblgen::emitSummary(StringRef summary
, raw_ostream
&os
) {
56 if (!summary
.empty()) {
57 llvm::StringRef trimmed
= summary
.trim();
58 char first
= std::toupper(trimmed
.front());
59 llvm::StringRef rest
= trimmed
.drop_front();
60 os
<< "\n_" << first
<< rest
<< "_\n\n";
64 // Emit the description by aligning the text to the left per line (e.g.,
65 // removing the minimum indentation across the block).
67 // This expects that the description in the tablegen file is already formatted
68 // in a way the user wanted but has some additional indenting due to being
69 // nested in the op definition.
70 void mlir::tblgen::emitDescription(StringRef description
, raw_ostream
&os
) {
71 raw_indented_ostream
ros(os
);
72 ros
.printReindented(description
.rtrim(" \t"));
75 void mlir::tblgen::emitDescriptionComment(StringRef description
,
76 raw_ostream
&os
, StringRef prefix
) {
77 if (description
.empty())
79 raw_indented_ostream
ros(os
);
80 StringRef trimmed
= description
.rtrim(" \t");
81 ros
.printReindented(trimmed
, (Twine(prefix
) + "/// ").str());
82 if (!trimmed
.endswith("\n"))
86 // Emits `str` with trailing newline if not empty.
87 static void emitIfNotEmpty(StringRef str
, raw_ostream
&os
) {
89 emitDescription(str
, os
);
94 /// Emit the given named constraint.
96 static void emitNamedConstraint(const T
&it
, raw_ostream
&os
) {
98 os
<< "| `" << it
.name
<< "`";
100 os
<< "«unnamed»";
101 os
<< " | " << it
.constraint
.getSummary() << "\n";
104 //===----------------------------------------------------------------------===//
105 // Operation Documentation
106 //===----------------------------------------------------------------------===//
108 /// Emit the assembly format of an operation.
109 static void emitAssemblyFormat(StringRef opName
, StringRef format
,
111 os
<< "\nSyntax:\n\n```\noperation ::= `" << opName
<< "` ";
113 // Print the assembly format aligned.
114 unsigned indent
= strlen("operation ::= ");
115 std::pair
<StringRef
, StringRef
> split
= format
.split('\n');
116 os
<< split
.first
.trim() << "\n";
118 split
= split
.second
.split('\n');
119 StringRef formatChunk
= split
.first
.trim();
120 if (!formatChunk
.empty())
121 os
.indent(indent
) << formatChunk
<< "\n";
122 } while (!split
.second
.empty());
126 static void emitOpTraitsDoc(const Operator
&op
, raw_ostream
&os
) {
127 // TODO: We should link to the trait/documentation of it. That also means we
128 // should add descriptions to traits that can be queried.
129 // Collect using set to sort effects, interfaces & traits.
130 std::set
<std::string
> effects
, interfaces
, traits
;
131 for (auto &trait
: op
.getTraits()) {
132 if (isa
<PredTrait
>(&trait
))
135 std::string name
= trait
.getDef().getName().str();
136 StringRef ref
= name
;
137 StringRef traitName
= trait
.getDef().getValueAsString("trait");
138 traitName
.consume_back("::Trait");
139 traitName
.consume_back("::Impl");
140 if (ref
.startswith("anonymous_"))
141 name
= traitName
.str();
142 if (isa
<InterfaceTrait
>(&trait
)) {
143 if (trait
.getDef().isSubClassOf("SideEffectsTraitBase")) {
144 auto effectName
= trait
.getDef().getValueAsString("baseEffectName");
145 effectName
.consume_front("::");
146 effectName
.consume_front("mlir::");
147 std::string effectStr
;
148 llvm::raw_string_ostream
os(effectStr
);
149 os
<< effectName
<< "{";
150 auto list
= trait
.getDef().getValueAsListOfDefs("effects");
151 llvm::interleaveComma(list
, os
, [&](Record
*rec
) {
152 StringRef effect
= rec
->getValueAsString("effect");
153 effect
.consume_front("::");
154 effect
.consume_front("mlir::");
155 os
<< effect
<< " on " << rec
->getValueAsString("resource");
158 effects
.insert(os
.str());
159 name
.append(llvm::formatv(" ({0})", traitName
).str());
161 interfaces
.insert(name
);
167 if (!traits
.empty()) {
168 llvm::interleaveComma(traits
, os
<< "\nTraits: ");
171 if (!interfaces
.empty()) {
172 llvm::interleaveComma(interfaces
, os
<< "\nInterfaces: ");
175 if (!effects
.empty()) {
176 llvm::interleaveComma(effects
, os
<< "\nEffects: ");
181 static StringRef
resolveAttrDescription(const Attribute
&attr
) {
182 StringRef description
= attr
.getDescription();
183 if (description
.empty())
184 return attr
.getBaseAttr().getDescription();
188 static void emitOpDoc(const Operator
&op
, raw_ostream
&os
) {
189 std::string classNameStr
= op
.getQualCppClassName();
190 StringRef className
= classNameStr
;
191 (void)className
.consume_front(stripPrefix
);
192 os
<< llvm::formatv("### `{0}` ({1})\n", op
.getOperationName(), className
);
194 // Emit the summary, syntax, and description if present.
196 emitSummary(op
.getSummary(), os
);
197 if (op
.hasAssemblyFormat())
198 emitAssemblyFormat(op
.getOperationName(), op
.getAssemblyFormat().trim(),
200 if (op
.hasDescription())
201 mlir::tblgen::emitDescription(op
.getDescription(), os
);
203 emitOpTraitsDoc(op
, os
);
206 if (op
.getNumAttributes() != 0) {
207 os
<< "\n#### Attributes:\n\n";
208 // Note: This table is HTML rather than markdown so the attribute's
209 // description can appear in an expandable region. The description may be
210 // multiple lines, which is not supported in a markdown table cell.
213 os
<< "<tr><th>Attribute</th><th>MLIR Type</th><th>Description</th></tr>\n";
214 for (const auto &it
: op
.getAttributes()) {
215 StringRef storageType
= it
.attr
.getStorageType();
216 // Name and storage type.
218 os
<< "<td><code>" << it
.name
<< "</code></td><td>" << storageType
220 StringRef description
= resolveAttrDescription(it
.attr
);
221 if (allowHugoSpecificFeatures
&& !description
.empty()) {
222 // Expandable description.
223 // This appears as just the summary, but when clicked shows the full
226 << "<summary>" << it
.attr
.getSummary() << "</summary>"
227 << "{{% markdown %}}" << description
<< "{{% /markdown %}}"
230 // Fallback: Single-line summary.
231 os
<< it
.attr
.getSummary();
233 os
<< "</td></tr>\n";
238 // Emit each of the operands.
239 if (op
.getNumOperands() != 0) {
240 os
<< "\n#### Operands:\n\n";
241 os
<< "| Operand | Description |\n"
242 << "| :-----: | ----------- |\n";
243 for (const auto &it
: op
.getOperands())
244 emitNamedConstraint(it
, os
);
248 if (op
.getNumResults() != 0) {
249 os
<< "\n#### Results:\n\n";
250 os
<< "| Result | Description |\n"
251 << "| :----: | ----------- |\n";
252 for (const auto &it
: op
.getResults())
253 emitNamedConstraint(it
, os
);
257 if (op
.getNumSuccessors() != 0) {
258 os
<< "\n#### Successors:\n\n";
259 os
<< "| Successor | Description |\n"
260 << "| :-------: | ----------- |\n";
261 for (const auto &it
: op
.getSuccessors())
262 emitNamedConstraint(it
, os
);
268 static void emitOpDoc(const RecordKeeper
&recordKeeper
, raw_ostream
&os
) {
269 auto opDefs
= getRequestedOpDefinitions(recordKeeper
);
271 os
<< "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
272 for (const llvm::Record
*opDef
: opDefs
)
273 emitOpDoc(Operator(opDef
), os
);
276 //===----------------------------------------------------------------------===//
277 // Attribute Documentation
278 //===----------------------------------------------------------------------===//
280 static void emitAttrDoc(const Attribute
&attr
, raw_ostream
&os
) {
281 os
<< "### " << attr
.getSummary() << "\n\n";
282 emitDescription(attr
.getDescription(), os
);
286 //===----------------------------------------------------------------------===//
287 // Type Documentation
288 //===----------------------------------------------------------------------===//
290 static void emitTypeDoc(const Type
&type
, raw_ostream
&os
) {
291 os
<< "### " << type
.getSummary() << "\n\n";
292 emitDescription(type
.getDescription(), os
);
296 //===----------------------------------------------------------------------===//
297 // TypeDef Documentation
298 //===----------------------------------------------------------------------===//
300 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef
&def
,
302 ArrayRef
<AttrOrTypeParameter
> parameters
= def
.getParameters();
303 char prefix
= isa
<AttrDef
>(def
) ? '#' : '!';
304 if (parameters
.empty()) {
305 os
<< "\nSyntax: `" << prefix
<< def
.getDialect().getName() << "."
306 << def
.getMnemonic() << "`\n";
310 os
<< "\nSyntax:\n\n```\n"
311 << prefix
<< def
.getDialect().getName() << "." << def
.getMnemonic()
313 for (const auto &it
: llvm::enumerate(parameters
)) {
314 const AttrOrTypeParameter
¶m
= it
.value();
315 os
<< " " << param
.getSyntax();
316 if (it
.index() < (parameters
.size() - 1))
318 os
<< " # " << param
.getName() << "\n";
323 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef
&def
, raw_ostream
&os
) {
324 os
<< llvm::formatv("### {0}\n", def
.getCppClassName());
326 // Emit the summary if present.
327 if (def
.hasSummary())
328 os
<< "\n" << def
.getSummary() << "\n";
330 // Emit the syntax if present.
331 if (def
.getMnemonic() && !def
.hasCustomAssemblyFormat())
332 emitAttrOrTypeDefAssemblyFormat(def
, os
);
334 // Emit the description if present.
335 if (def
.hasDescription()) {
337 mlir::tblgen::emitDescription(def
.getDescription(), os
);
340 // Emit parameter documentation.
341 ArrayRef
<AttrOrTypeParameter
> parameters
= def
.getParameters();
342 if (!parameters
.empty()) {
343 os
<< "\n#### Parameters:\n\n";
344 os
<< "| Parameter | C++ type | Description |\n"
345 << "| :-------: | :-------: | ----------- |\n";
346 for (const auto &it
: parameters
) {
347 auto desc
= it
.getSummary();
348 os
<< "| " << it
.getName() << " | `" << it
.getCppType() << "` | "
349 << (desc
? *desc
: "") << " |\n";
356 static void emitAttrOrTypeDefDoc(const RecordKeeper
&recordKeeper
,
357 raw_ostream
&os
, StringRef recordTypeName
) {
358 std::vector
<llvm::Record
*> defs
=
359 recordKeeper
.getAllDerivedDefinitions(recordTypeName
);
361 os
<< "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
362 for (const llvm::Record
*def
: defs
)
363 emitAttrOrTypeDefDoc(AttrOrTypeDef(def
), os
);
366 //===----------------------------------------------------------------------===//
367 // Dialect Documentation
368 //===----------------------------------------------------------------------===//
371 const Dialect
&getDialect() const { return ops
.front().getDialect(); }
373 // Returns the summary description of the section.
374 std::string summary
= "";
376 // Returns the description of the section.
377 StringRef description
= "";
379 // Instances inside the section.
380 std::vector
<Operator
> ops
;
383 static void maybeNest(bool nest
, llvm::function_ref
<void(raw_ostream
&os
)> fn
,
386 llvm::raw_string_ostream
ss(str
);
388 for (StringRef x
: llvm::split(ss
.str(), "\n")) {
389 if (nest
&& x
.starts_with("#"))
395 static void emitBlock(ArrayRef
<Attribute
> attributes
,
396 ArrayRef
<AttrDef
> attrDefs
, ArrayRef
<OpDocGroup
> ops
,
397 ArrayRef
<Type
> types
, ArrayRef
<TypeDef
> typeDefs
,
400 os
<< "## Operation definition\n\n";
401 for (const OpDocGroup
&grouping
: ops
) {
402 bool nested
= !grouping
.summary
.empty();
405 [&](raw_ostream
&os
) {
407 os
<< "## " << StringRef(grouping
.summary
).trim() << "\n\n";
408 emitDescription(grouping
.description
, os
);
411 for (const Operator
&op
: grouping
.ops
) {
419 if (!attributes
.empty()) {
420 os
<< "## Attribute constraint definition\n\n";
421 for (const Attribute
&attr
: attributes
)
422 emitAttrDoc(attr
, os
);
425 if (!attrDefs
.empty()) {
426 os
<< "## Attribute definition\n\n";
427 for (const AttrDef
&def
: attrDefs
)
428 emitAttrOrTypeDefDoc(def
, os
);
431 // TODO: Add link between use and def for types
432 if (!types
.empty()) {
433 os
<< "## Type constraint definition\n\n";
434 for (const Type
&type
: types
)
435 emitTypeDoc(type
, os
);
438 if (!typeDefs
.empty()) {
439 os
<< "## Type definition\n\n";
440 for (const TypeDef
&def
: typeDefs
)
441 emitAttrOrTypeDefDoc(def
, os
);
445 static void emitDialectDoc(const Dialect
&dialect
,
446 ArrayRef
<Attribute
> attributes
,
447 ArrayRef
<AttrDef
> attrDefs
, ArrayRef
<OpDocGroup
> ops
,
448 ArrayRef
<Type
> types
, ArrayRef
<TypeDef
> typeDefs
,
450 os
<< "# '" << dialect
.getName() << "' Dialect\n\n";
451 emitIfNotEmpty(dialect
.getSummary(), os
);
452 emitIfNotEmpty(dialect
.getDescription(), os
);
454 // Generate a TOC marker except if description already contains one.
455 llvm::Regex
r("^[[:space:]]*\\[TOC\\]$", llvm::Regex::RegexFlags::Newline
);
456 if (!r
.match(dialect
.getDescription()))
459 emitBlock(attributes
, attrDefs
, ops
, types
, typeDefs
, os
);
462 static bool emitDialectDoc(const RecordKeeper
&recordKeeper
, raw_ostream
&os
) {
463 std::vector
<Record
*> dialectDefs
=
464 recordKeeper
.getAllDerivedDefinitionsIfDefined("Dialect");
465 SmallVector
<Dialect
> dialects(dialectDefs
.begin(), dialectDefs
.end());
466 std::optional
<Dialect
> dialect
= findDialectToGenerate(dialects
);
470 std::vector
<Record
*> opDefs
= getRequestedOpDefinitions(recordKeeper
);
471 std::vector
<Record
*> attrDefs
=
472 recordKeeper
.getAllDerivedDefinitionsIfDefined("DialectAttr");
473 std::vector
<Record
*> typeDefs
=
474 recordKeeper
.getAllDerivedDefinitionsIfDefined("DialectType");
475 std::vector
<Record
*> typeDefDefs
=
476 recordKeeper
.getAllDerivedDefinitionsIfDefined("TypeDef");
477 std::vector
<Record
*> attrDefDefs
=
478 recordKeeper
.getAllDerivedDefinitionsIfDefined("AttrDef");
480 std::vector
<Attribute
> dialectAttrs
;
481 std::vector
<AttrDef
> dialectAttrDefs
;
482 std::vector
<OpDocGroup
> dialectOps
;
483 std::vector
<Type
> dialectTypes
;
484 std::vector
<TypeDef
> dialectTypeDefs
;
486 llvm::SmallDenseSet
<Record
*> seen
;
487 auto addIfInDialect
= [&](llvm::Record
*record
, const auto &def
, auto &vec
) {
488 if (seen
.insert(record
).second
&& def
.getDialect() == *dialect
) {
495 SmallDenseMap
<Record
*, OpDocGroup
> opDocGroup
;
497 for (Record
*def
: attrDefDefs
)
498 addIfInDialect(def
, AttrDef(def
), dialectAttrDefs
);
499 for (Record
*def
: attrDefs
)
500 addIfInDialect(def
, Attribute(def
), dialectAttrs
);
501 for (Record
*def
: opDefs
) {
502 if (Record
*group
= def
->getValueAsOptionalDef("opDocGroup")) {
503 OpDocGroup
&op
= opDocGroup
[group
];
504 addIfInDialect(def
, Operator(def
), op
.ops
);
507 op
.ops
.emplace_back(def
);
508 addIfInDialect(def
, op
, dialectOps
);
512 recordKeeper
.getAllDerivedDefinitionsIfDefined("OpDocGroup")) {
513 if (opDocGroup
[rec
].ops
.empty())
515 opDocGroup
[rec
].summary
= rec
->getValueAsString("summary");
516 opDocGroup
[rec
].description
= rec
->getValueAsString("description");
517 dialectOps
.push_back(opDocGroup
[rec
]);
519 for (Record
*def
: typeDefDefs
)
520 addIfInDialect(def
, TypeDef(def
), dialectTypeDefs
);
521 for (Record
*def
: typeDefs
)
522 addIfInDialect(def
, Type(def
), dialectTypes
);
524 // Sort alphabetically ignorning dialect for ops and section name for
526 // TODO: The sorting order could be revised, currently attempting to sort of
527 // keep in alphabetical order.
528 std::sort(dialectOps
.begin(), dialectOps
.end(),
529 [](const OpDocGroup
&lhs
, const OpDocGroup
&rhs
) {
530 auto getDesc
= [](const OpDocGroup
&arg
) -> StringRef
{
531 if (!arg
.summary
.empty())
533 return arg
.ops
.front().getDef().getValueAsString("opName");
535 return getDesc(lhs
).compare_insensitive(getDesc(rhs
)) < 0;
538 os
<< "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n";
539 emitDialectDoc(*dialect
, dialectAttrs
, dialectAttrDefs
, dialectOps
,
540 dialectTypes
, dialectTypeDefs
, os
);
544 //===----------------------------------------------------------------------===//
546 //===----------------------------------------------------------------------===//
548 static mlir::GenRegistration
549 genAttrRegister("gen-attrdef-doc",
550 "Generate dialect attribute documentation",
551 [](const RecordKeeper
&records
, raw_ostream
&os
) {
552 emitAttrOrTypeDefDoc(records
, os
, "AttrDef");
556 static mlir::GenRegistration
557 genOpRegister("gen-op-doc", "Generate dialect documentation",
558 [](const RecordKeeper
&records
, raw_ostream
&os
) {
559 emitOpDoc(records
, os
);
563 static mlir::GenRegistration
564 genTypeRegister("gen-typedef-doc", "Generate dialect type documentation",
565 [](const RecordKeeper
&records
, raw_ostream
&os
) {
566 emitAttrOrTypeDefDoc(records
, os
, "TypeDef");
570 static mlir::GenRegistration
571 genRegister("gen-dialect-doc", "Generate dialect documentation",
572 [](const RecordKeeper
&records
, raw_ostream
&os
) {
573 return emitDialectDoc(records
, os
);