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 << "&laquo;unnamed&raquo;: ";
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 << "&laquo;unnamed&raquo;: ";
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