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