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