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 "OpGenHelpers.h" 16 #include "mlir/Support/IndentedOstream.h" 17 #include "mlir/TableGen/AttrOrTypeDef.h" 18 #include "mlir/TableGen/GenInfo.h" 19 #include "mlir/TableGen/Operator.h" 20 #include "llvm/ADT/DenseMap.h" 21 #include "llvm/ADT/StringExtras.h" 22 #include "llvm/Support/CommandLine.h" 23 #include "llvm/Support/FormatVariadic.h" 24 #include "llvm/Support/Signals.h" 25 #include "llvm/TableGen/Error.h" 26 #include "llvm/TableGen/Record.h" 27 #include "llvm/TableGen/TableGenBackend.h" 28 29 #include <set> 30 31 using namespace llvm; 32 using namespace mlir; 33 using namespace mlir::tblgen; 34 35 using mlir::tblgen::Operator; 36 37 extern llvm::cl::opt<std::string> selectedDialect; 38 39 // Emit the description by aligning the text to the left per line (e.g., 40 // removing the minimum indentation across the block). 41 // 42 // This expects that the description in the tablegen file is already formatted 43 // in a way the user wanted but has some additional indenting due to being 44 // nested in the op definition. 45 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) { 46 raw_indented_ostream ros(os); 47 ros.reindent(description.rtrim(" \t")); 48 } 49 50 // Emits `str` with trailing newline if not empty. 51 static void emitIfNotEmpty(StringRef str, raw_ostream &os) { 52 if (!str.empty()) { 53 emitDescription(str, os); 54 os << "\n"; 55 } 56 } 57 58 /// Emit the given named constraint. 59 template <typename T> 60 static void emitNamedConstraint(const T &it, raw_ostream &os) { 61 if (!it.name.empty()) 62 os << "`" << it.name << "`"; 63 else 64 os << "«unnamed»"; 65 os << " | " << it.constraint.getSummary() << "\n"; 66 } 67 68 //===----------------------------------------------------------------------===// 69 // Operation Documentation 70 //===----------------------------------------------------------------------===// 71 72 /// Emit the assembly format of an operation. 73 static void emitAssemblyFormat(StringRef opName, StringRef format, 74 raw_ostream &os) { 75 os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` "; 76 77 // Print the assembly format aligned. 78 unsigned indent = strlen("operation ::= "); 79 std::pair<StringRef, StringRef> split = format.split('\n'); 80 os << split.first.trim() << "\n"; 81 do { 82 split = split.second.split('\n'); 83 StringRef formatChunk = split.first.trim(); 84 if (!formatChunk.empty()) 85 os.indent(indent) << formatChunk << "\n"; 86 } while (!split.second.empty()); 87 os << "```\n\n"; 88 } 89 90 static void emitOpDoc(Operator op, raw_ostream &os) { 91 os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(), 92 op.getQualCppClassName()); 93 94 // Emit the summary, syntax, and description if present. 95 if (op.hasSummary()) 96 os << "\n" << op.getSummary() << "\n\n"; 97 if (op.hasAssemblyFormat()) 98 emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(), 99 os); 100 if (op.hasDescription()) 101 mlir::tblgen::emitDescription(op.getDescription(), os); 102 103 // Emit attributes. 104 if (op.getNumAttributes() != 0) { 105 // TODO: Attributes are only documented by TableGen name, with no further 106 // info. This should be improved. 107 os << "\n#### Attributes:\n\n"; 108 os << "| Attribute | MLIR Type | Description |\n" 109 << "| :-------: | :-------: | ----------- |\n"; 110 for (const auto &it : op.getAttributes()) { 111 StringRef storageType = it.attr.getStorageType(); 112 os << "`" << it.name << "` | " << storageType << " | " 113 << it.attr.getSummary() << "\n"; 114 } 115 } 116 117 // Emit each of the operands. 118 if (op.getNumOperands() != 0) { 119 os << "\n#### Operands:\n\n"; 120 os << "| Operand | Description |\n" 121 << "| :-----: | ----------- |\n"; 122 for (const auto &it : op.getOperands()) 123 emitNamedConstraint(it, os); 124 } 125 126 // Emit results. 127 if (op.getNumResults() != 0) { 128 os << "\n#### Results:\n\n"; 129 os << "| Result | Description |\n" 130 << "| :----: | ----------- |\n"; 131 for (const auto &it : op.getResults()) 132 emitNamedConstraint(it, os); 133 } 134 135 // Emit successors. 136 if (op.getNumSuccessors() != 0) { 137 os << "\n#### Successors:\n\n"; 138 os << "| Successor | Description |\n" 139 << "| :-------: | ----------- |\n"; 140 for (const auto &it : op.getSuccessors()) 141 emitNamedConstraint(it, os); 142 } 143 144 os << "\n"; 145 } 146 147 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 148 auto opDefs = getRequestedOpDefinitions(recordKeeper); 149 150 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 151 for (const llvm::Record *opDef : opDefs) 152 emitOpDoc(Operator(opDef), os); 153 } 154 155 //===----------------------------------------------------------------------===// 156 // Type Documentation 157 //===----------------------------------------------------------------------===// 158 159 static void emitTypeDoc(const Type &type, raw_ostream &os) { 160 os << "### " << type.getSummary() << "\n"; 161 emitDescription(type.getDescription(), os); 162 os << "\n"; 163 } 164 165 //===----------------------------------------------------------------------===// 166 // TypeDef Documentation 167 //===----------------------------------------------------------------------===// 168 169 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def, 170 raw_ostream &os) { 171 SmallVector<AttrOrTypeParameter, 4> parameters; 172 def.getParameters(parameters); 173 if (parameters.empty()) { 174 os << "\nSyntax: `!" << def.getDialect().getName() << "." 175 << def.getMnemonic() << "`\n"; 176 return; 177 } 178 179 os << "\nSyntax:\n\n```\n!" << def.getDialect().getName() << "." 180 << def.getMnemonic() << "<\n"; 181 for (auto it : llvm::enumerate(parameters)) { 182 const AttrOrTypeParameter ¶m = it.value(); 183 os << " " << param.getSyntax(); 184 if (it.index() < (parameters.size() - 1)) 185 os << ","; 186 os << " # " << param.getName() << "\n"; 187 } 188 os << ">\n```\n"; 189 } 190 191 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef &def, raw_ostream &os) { 192 os << llvm::formatv("### {0}\n", def.getCppClassName()); 193 194 // Emit the summary if present. 195 if (def.hasSummary()) 196 os << "\n" << def.getSummary() << "\n"; 197 198 // Emit the syntax if present. 199 if (def.getMnemonic() && def.getPrinterCode() == StringRef() && 200 def.getParserCode() == StringRef()) 201 emitAttrOrTypeDefAssemblyFormat(def, os); 202 203 // Emit the description if present. 204 if (def.hasDescription()) { 205 os << "\n"; 206 mlir::tblgen::emitDescription(def.getDescription(), os); 207 } 208 209 // Emit parameter documentation. 210 SmallVector<AttrOrTypeParameter, 4> parameters; 211 def.getParameters(parameters); 212 if (!parameters.empty()) { 213 os << "\n#### Parameters:\n\n"; 214 os << "| Parameter | C++ type | Description |\n" 215 << "| :-------: | :-------: | ----------- |\n"; 216 for (const auto &it : parameters) { 217 auto desc = it.getSummary(); 218 os << "| " << it.getName() << " | `" << it.getCppType() << "` | " 219 << (desc ? *desc : "") << " |\n"; 220 } 221 } 222 223 os << "\n"; 224 } 225 226 static void emitAttrOrTypeDefDoc(const RecordKeeper &recordKeeper, 227 raw_ostream &os, StringRef recordTypeName) { 228 std::vector<llvm::Record *> defs = 229 recordKeeper.getAllDerivedDefinitions(recordTypeName); 230 231 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 232 for (const llvm::Record *def : defs) 233 emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os); 234 } 235 236 //===----------------------------------------------------------------------===// 237 // Dialect Documentation 238 //===----------------------------------------------------------------------===// 239 240 static void emitDialectDoc(const Dialect &dialect, ArrayRef<AttrDef> attrDefs, 241 ArrayRef<Operator> ops, ArrayRef<Type> types, 242 ArrayRef<TypeDef> typeDefs, raw_ostream &os) { 243 if (selectedDialect.getNumOccurrences() && 244 dialect.getName() != selectedDialect) 245 return; 246 os << "# '" << dialect.getName() << "' Dialect\n\n"; 247 emitIfNotEmpty(dialect.getSummary(), os); 248 emitIfNotEmpty(dialect.getDescription(), os); 249 250 os << "[TOC]\n\n"; 251 252 if (!attrDefs.empty()) { 253 os << "## Attribute definition\n\n"; 254 for (const AttrDef &def : attrDefs) 255 emitAttrOrTypeDefDoc(def, os); 256 } 257 258 // TODO: Add link between use and def for types 259 if (!types.empty()) { 260 os << "## Type constraint definition\n\n"; 261 for (const Type &type : types) 262 emitTypeDoc(type, os); 263 } 264 265 if (!ops.empty()) { 266 os << "## Operation definition\n\n"; 267 for (const Operator &op : ops) 268 emitOpDoc(op, os); 269 } 270 271 if (!typeDefs.empty()) { 272 os << "## Type definition\n\n"; 273 for (const TypeDef &def : typeDefs) 274 emitAttrOrTypeDefDoc(def, os); 275 } 276 } 277 278 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 279 std::vector<Record *> opDefs = getRequestedOpDefinitions(recordKeeper); 280 std::vector<Record *> typeDefs = 281 recordKeeper.getAllDerivedDefinitions("DialectType"); 282 std::vector<Record *> typeDefDefs = 283 recordKeeper.getAllDerivedDefinitions("TypeDef"); 284 std::vector<Record *> attrDefDefs = 285 recordKeeper.getAllDerivedDefinitions("AttrDef"); 286 287 std::set<Dialect> dialectsWithDocs; 288 289 llvm::StringMap<std::vector<AttrDef>> dialectAttrDefs; 290 llvm::StringMap<std::vector<Operator>> dialectOps; 291 llvm::StringMap<std::vector<Type>> dialectTypes; 292 llvm::StringMap<std::vector<TypeDef>> dialectTypeDefs; 293 for (auto *attrDef : attrDefDefs) { 294 AttrDef attr(attrDef); 295 dialectAttrDefs[attr.getDialect().getName()].push_back(attr); 296 dialectsWithDocs.insert(attr.getDialect()); 297 } 298 for (auto *opDef : opDefs) { 299 Operator op(opDef); 300 dialectOps[op.getDialect().getName()].push_back(op); 301 dialectsWithDocs.insert(op.getDialect()); 302 } 303 for (auto *typeDef : typeDefs) { 304 Type type(typeDef); 305 if (auto dialect = type.getDialect()) 306 dialectTypes[dialect.getName()].push_back(type); 307 } 308 for (auto *typeDef : typeDefDefs) { 309 TypeDef type(typeDef); 310 dialectTypeDefs[type.getDialect().getName()].push_back(type); 311 dialectsWithDocs.insert(type.getDialect()); 312 } 313 314 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 315 for (const Dialect &dialect : dialectsWithDocs) { 316 StringRef dialectName = dialect.getName(); 317 emitDialectDoc(dialect, dialectAttrDefs[dialectName], 318 dialectOps[dialectName], dialectTypes[dialectName], 319 dialectTypeDefs[dialectName], os); 320 } 321 } 322 323 //===----------------------------------------------------------------------===// 324 // Gen Registration 325 //===----------------------------------------------------------------------===// 326 327 static mlir::GenRegistration 328 genAttrRegister("gen-attrdef-doc", 329 "Generate dialect attribute documentation", 330 [](const RecordKeeper &records, raw_ostream &os) { 331 emitAttrOrTypeDefDoc(records, os, "AttrDef"); 332 return false; 333 }); 334 335 static mlir::GenRegistration 336 genOpRegister("gen-op-doc", "Generate dialect documentation", 337 [](const RecordKeeper &records, raw_ostream &os) { 338 emitOpDoc(records, os); 339 return false; 340 }); 341 342 static mlir::GenRegistration 343 genTypeRegister("gen-typedef-doc", "Generate dialect type documentation", 344 [](const RecordKeeper &records, raw_ostream &os) { 345 emitAttrOrTypeDefDoc(records, os, "TypeDef"); 346 return false; 347 }); 348 349 static mlir::GenRegistration 350 genRegister("gen-dialect-doc", "Generate dialect documentation", 351 [](const RecordKeeper &records, raw_ostream &os) { 352 emitDialectDoc(records, os); 353 return false; 354 }); 355