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 "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/Attribute.h" 20 #include "mlir/TableGen/GenInfo.h" 21 #include "mlir/TableGen/Operator.h" 22 #include "llvm/ADT/DenseMap.h" 23 #include "llvm/ADT/SetVector.h" 24 #include "llvm/ADT/StringExtras.h" 25 #include "llvm/ADT/StringRef.h" 26 #include "llvm/Support/CommandLine.h" 27 #include "llvm/Support/FormatVariadic.h" 28 #include "llvm/Support/Regex.h" 29 #include "llvm/Support/Signals.h" 30 #include "llvm/TableGen/Error.h" 31 #include "llvm/TableGen/Record.h" 32 #include "llvm/TableGen/TableGenBackend.h" 33 34 #include <set> 35 #include <string> 36 37 //===----------------------------------------------------------------------===// 38 // Commandline Options 39 //===----------------------------------------------------------------------===// 40 static llvm::cl::OptionCategory 41 docCat("Options for -gen-(attrdef|typedef|enum|op|dialect)-doc"); 42 llvm::cl::opt<std::string> 43 stripPrefix("strip-prefix", 44 llvm::cl::desc("Strip prefix of the fully qualified names"), 45 llvm::cl::init("::mlir::"), llvm::cl::cat(docCat)); 46 llvm::cl::opt<bool> allowHugoSpecificFeatures( 47 "allow-hugo-specific-features", 48 llvm::cl::desc("Allows using features specific to Hugo"), 49 llvm::cl::init(false), llvm::cl::cat(docCat)); 50 51 using namespace llvm; 52 using namespace mlir; 53 using namespace mlir::tblgen; 54 using mlir::tblgen::Operator; 55 56 void mlir::tblgen::emitSummary(StringRef summary, raw_ostream &os) { 57 if (!summary.empty()) { 58 llvm::StringRef trimmed = summary.trim(); 59 char first = std::toupper(trimmed.front()); 60 llvm::StringRef rest = trimmed.drop_front(); 61 os << "\n_" << first << rest << "_\n\n"; 62 } 63 } 64 65 // Emit the description by aligning the text to the left per line (e.g., 66 // removing the minimum indentation across the block). 67 // 68 // This expects that the description in the tablegen file is already formatted 69 // in a way the user wanted but has some additional indenting due to being 70 // nested in the op definition. 71 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) { 72 raw_indented_ostream ros(os); 73 ros.printReindented(description.rtrim(" \t")); 74 } 75 76 void mlir::tblgen::emitDescriptionComment(StringRef description, 77 raw_ostream &os, StringRef prefix) { 78 if (description.empty()) 79 return; 80 raw_indented_ostream ros(os); 81 StringRef trimmed = description.rtrim(" \t"); 82 ros.printReindented(trimmed, (Twine(prefix) + "/// ").str()); 83 if (!trimmed.ends_with("\n")) 84 ros << "\n"; 85 } 86 87 // Emits `str` with trailing newline if not empty. 88 static void emitIfNotEmpty(StringRef str, raw_ostream &os) { 89 if (!str.empty()) { 90 emitDescription(str, os); 91 os << "\n"; 92 } 93 } 94 95 /// Emit the given named constraint. 96 template <typename T> 97 static void emitNamedConstraint(const T &it, raw_ostream &os) { 98 if (!it.name.empty()) 99 os << "| `" << it.name << "`"; 100 else 101 os << "«unnamed»"; 102 os << " | " << it.constraint.getSummary() << "\n"; 103 } 104 105 //===----------------------------------------------------------------------===// 106 // Operation Documentation 107 //===----------------------------------------------------------------------===// 108 109 /// Emit the assembly format of an operation. 110 static void emitAssemblyFormat(StringRef opName, StringRef format, 111 raw_ostream &os) { 112 os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` "; 113 114 // Print the assembly format aligned. 115 unsigned indent = strlen("operation ::= "); 116 std::pair<StringRef, StringRef> split = format.split('\n'); 117 os << split.first.trim() << "\n"; 118 do { 119 split = split.second.split('\n'); 120 StringRef formatChunk = split.first.trim(); 121 if (!formatChunk.empty()) 122 os.indent(indent) << formatChunk << "\n"; 123 } while (!split.second.empty()); 124 os << "```\n\n"; 125 } 126 127 /// Place `text` between backticks so that the Markdown processor renders it as 128 /// inline code. 129 static std::string backticks(const std::string &text) { 130 return '`' + text + '`'; 131 } 132 133 static void emitOpTraitsDoc(const Operator &op, raw_ostream &os) { 134 // TODO: We should link to the trait/documentation of it. That also means we 135 // should add descriptions to traits that can be queried. 136 // Collect using set to sort effects, interfaces & traits. 137 std::set<std::string> effects, interfaces, traits; 138 for (auto &trait : op.getTraits()) { 139 if (isa<PredTrait>(&trait)) 140 continue; 141 142 std::string name = trait.getDef().getName().str(); 143 StringRef ref = name; 144 StringRef traitName = trait.getDef().getValueAsString("trait"); 145 traitName.consume_back("::Trait"); 146 traitName.consume_back("::Impl"); 147 if (ref.starts_with("anonymous_")) 148 name = traitName.str(); 149 if (isa<InterfaceTrait>(&trait)) { 150 if (trait.getDef().isSubClassOf("SideEffectsTraitBase")) { 151 auto effectName = trait.getDef().getValueAsString("baseEffectName"); 152 effectName.consume_front("::"); 153 effectName.consume_front("mlir::"); 154 std::string effectStr; 155 llvm::raw_string_ostream os(effectStr); 156 os << effectName << "{"; 157 auto list = trait.getDef().getValueAsListOfDefs("effects"); 158 llvm::interleaveComma(list, os, [&](Record *rec) { 159 StringRef effect = rec->getValueAsString("effect"); 160 effect.consume_front("::"); 161 effect.consume_front("mlir::"); 162 os << effect << " on " << rec->getValueAsString("resource"); 163 }); 164 os << "}"; 165 effects.insert(backticks(os.str())); 166 name.append(llvm::formatv(" ({0})", traitName).str()); 167 } 168 interfaces.insert(backticks(name)); 169 continue; 170 } 171 172 traits.insert(backticks(name)); 173 } 174 if (!traits.empty()) { 175 llvm::interleaveComma(traits, os << "\nTraits: "); 176 os << "\n"; 177 } 178 if (!interfaces.empty()) { 179 llvm::interleaveComma(interfaces, os << "\nInterfaces: "); 180 os << "\n"; 181 } 182 if (!effects.empty()) { 183 llvm::interleaveComma(effects, os << "\nEffects: "); 184 os << "\n"; 185 } 186 } 187 188 static StringRef resolveAttrDescription(const Attribute &attr) { 189 StringRef description = attr.getDescription(); 190 if (description.empty()) 191 return attr.getBaseAttr().getDescription(); 192 return description; 193 } 194 195 static void emitOpDoc(const Operator &op, raw_ostream &os) { 196 std::string classNameStr = op.getQualCppClassName(); 197 StringRef className = classNameStr; 198 (void)className.consume_front(stripPrefix); 199 os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(), className); 200 201 // Emit the summary, syntax, and description if present. 202 if (op.hasSummary()) 203 emitSummary(op.getSummary(), os); 204 if (op.hasAssemblyFormat()) 205 emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(), 206 os); 207 if (op.hasDescription()) 208 mlir::tblgen::emitDescription(op.getDescription(), os); 209 210 emitOpTraitsDoc(op, os); 211 212 // Emit attributes. 213 if (op.getNumAttributes() != 0) { 214 os << "\n#### Attributes:\n\n"; 215 // Note: This table is HTML rather than markdown so the attribute's 216 // description can appear in an expandable region. The description may be 217 // multiple lines, which is not supported in a markdown table cell. 218 os << "<table>\n"; 219 // Header. 220 os << "<tr><th>Attribute</th><th>MLIR Type</th><th>Description</th></tr>\n"; 221 for (const auto &it : op.getAttributes()) { 222 StringRef storageType = it.attr.getStorageType(); 223 // Name and storage type. 224 os << "<tr>"; 225 os << "<td><code>" << it.name << "</code></td><td>" << storageType 226 << "</td><td>"; 227 StringRef description = resolveAttrDescription(it.attr); 228 if (allowHugoSpecificFeatures && !description.empty()) { 229 // Expandable description. 230 // This appears as just the summary, but when clicked shows the full 231 // description. 232 os << "<details>" << "<summary>" << it.attr.getSummary() << "</summary>" 233 << "{{% markdown %}}" << description << "{{% /markdown %}}" 234 << "</details>"; 235 } else { 236 // Fallback: Single-line summary. 237 os << it.attr.getSummary(); 238 } 239 os << "</td></tr>\n"; 240 } 241 os << "</table>\n"; 242 } 243 244 // Emit each of the operands. 245 if (op.getNumOperands() != 0) { 246 os << "\n#### Operands:\n\n"; 247 os << "| Operand | Description |\n" 248 << "| :-----: | ----------- |\n"; 249 for (const auto &it : op.getOperands()) 250 emitNamedConstraint(it, os); 251 } 252 253 // Emit results. 254 if (op.getNumResults() != 0) { 255 os << "\n#### Results:\n\n"; 256 os << "| Result | Description |\n" 257 << "| :----: | ----------- |\n"; 258 for (const auto &it : op.getResults()) 259 emitNamedConstraint(it, os); 260 } 261 262 // Emit successors. 263 if (op.getNumSuccessors() != 0) { 264 os << "\n#### Successors:\n\n"; 265 os << "| Successor | Description |\n" 266 << "| :-------: | ----------- |\n"; 267 for (const auto &it : op.getSuccessors()) 268 emitNamedConstraint(it, os); 269 } 270 271 os << "\n"; 272 } 273 274 static void emitSourceLink(StringRef inputFilename, raw_ostream &os) { 275 size_t pathBegin = inputFilename.find("mlir/include/mlir/"); 276 if (pathBegin == StringRef::npos) 277 return; 278 279 StringRef inputFromMlirInclude = inputFilename.substr(pathBegin); 280 281 os << "[source](https://github.com/llvm/llvm-project/blob/main/" 282 << inputFromMlirInclude << ")\n\n"; 283 } 284 285 static void emitOpDoc(RecordKeeper &recordKeeper, raw_ostream &os) { 286 auto opDefs = getRequestedOpDefinitions(recordKeeper); 287 288 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 289 emitSourceLink(recordKeeper.getInputFilename(), os); 290 for (const llvm::Record *opDef : opDefs) 291 emitOpDoc(Operator(opDef), os); 292 } 293 294 //===----------------------------------------------------------------------===// 295 // Attribute Documentation 296 //===----------------------------------------------------------------------===// 297 298 static void emitAttrDoc(const Attribute &attr, raw_ostream &os) { 299 os << "### " << attr.getSummary() << "\n\n"; 300 emitDescription(attr.getDescription(), os); 301 os << "\n\n"; 302 } 303 304 //===----------------------------------------------------------------------===// 305 // Type Documentation 306 //===----------------------------------------------------------------------===// 307 308 static void emitTypeDoc(const Type &type, raw_ostream &os) { 309 os << "### " << type.getSummary() << "\n\n"; 310 emitDescription(type.getDescription(), os); 311 os << "\n\n"; 312 } 313 314 //===----------------------------------------------------------------------===// 315 // TypeDef Documentation 316 //===----------------------------------------------------------------------===// 317 318 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def, 319 raw_ostream &os) { 320 ArrayRef<AttrOrTypeParameter> parameters = def.getParameters(); 321 char prefix = isa<AttrDef>(def) ? '#' : '!'; 322 if (parameters.empty()) { 323 os << "\nSyntax: `" << prefix << def.getDialect().getName() << "." 324 << def.getMnemonic() << "`\n"; 325 return; 326 } 327 328 os << "\nSyntax:\n\n```\n" 329 << prefix << def.getDialect().getName() << "." << def.getMnemonic() 330 << "<\n"; 331 for (const auto &it : llvm::enumerate(parameters)) { 332 const AttrOrTypeParameter ¶m = it.value(); 333 os << " " << param.getSyntax(); 334 if (it.index() < (parameters.size() - 1)) 335 os << ","; 336 os << " # " << param.getName() << "\n"; 337 } 338 os << ">\n```\n"; 339 } 340 341 static void emitAttrOrTypeDefDoc(const AttrOrTypeDef &def, raw_ostream &os) { 342 os << llvm::formatv("### {0}\n", def.getCppClassName()); 343 344 // Emit the summary if present. 345 if (def.hasSummary()) 346 os << "\n" << def.getSummary() << "\n"; 347 348 // Emit the syntax if present. 349 if (def.getMnemonic() && !def.hasCustomAssemblyFormat()) 350 emitAttrOrTypeDefAssemblyFormat(def, os); 351 352 // Emit the description if present. 353 if (def.hasDescription()) { 354 os << "\n"; 355 mlir::tblgen::emitDescription(def.getDescription(), os); 356 } 357 358 // Emit parameter documentation. 359 ArrayRef<AttrOrTypeParameter> parameters = def.getParameters(); 360 if (!parameters.empty()) { 361 os << "\n#### Parameters:\n\n"; 362 os << "| Parameter | C++ type | Description |\n" 363 << "| :-------: | :-------: | ----------- |\n"; 364 for (const auto &it : parameters) { 365 auto desc = it.getSummary(); 366 os << "| " << it.getName() << " | `" << it.getCppType() << "` | " 367 << (desc ? *desc : "") << " |\n"; 368 } 369 } 370 371 os << "\n"; 372 } 373 374 static void emitAttrOrTypeDefDoc(RecordKeeper &recordKeeper, raw_ostream &os, 375 StringRef recordTypeName) { 376 std::vector<llvm::Record *> defs = 377 recordKeeper.getAllDerivedDefinitions(recordTypeName); 378 379 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 380 for (const llvm::Record *def : defs) 381 emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os); 382 } 383 384 //===----------------------------------------------------------------------===// 385 // Enum Documentation 386 //===----------------------------------------------------------------------===// 387 388 static void emitEnumDoc(const EnumAttr &def, raw_ostream &os) { 389 os << llvm::formatv("### {0}\n", def.getEnumClassName()); 390 391 // Emit the summary if present. 392 if (!def.getSummary().empty()) 393 os << "\n" << def.getSummary() << "\n"; 394 395 // Emit case documentation. 396 std::vector<EnumAttrCase> cases = def.getAllCases(); 397 os << "\n#### Cases:\n\n"; 398 os << "| Symbol | Value | String |\n" 399 << "| :----: | :---: | ------ |\n"; 400 for (const auto &it : cases) { 401 os << "| " << it.getSymbol() << " | `" << it.getValue() << "` | " 402 << it.getStr() << " |\n"; 403 } 404 405 os << "\n"; 406 } 407 408 static void emitEnumDoc(RecordKeeper &recordKeeper, raw_ostream &os) { 409 std::vector<llvm::Record *> defs = 410 recordKeeper.getAllDerivedDefinitions("EnumAttr"); 411 412 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 413 for (const llvm::Record *def : defs) 414 emitEnumDoc(EnumAttr(def), os); 415 } 416 417 //===----------------------------------------------------------------------===// 418 // Dialect Documentation 419 //===----------------------------------------------------------------------===// 420 421 struct OpDocGroup { 422 const Dialect &getDialect() const { return ops.front().getDialect(); } 423 424 // Returns the summary description of the section. 425 std::string summary = ""; 426 427 // Returns the description of the section. 428 StringRef description = ""; 429 430 // Instances inside the section. 431 std::vector<Operator> ops; 432 }; 433 434 static void maybeNest(bool nest, llvm::function_ref<void(raw_ostream &os)> fn, 435 raw_ostream &os) { 436 std::string str; 437 llvm::raw_string_ostream ss(str); 438 fn(ss); 439 for (StringRef x : llvm::split(ss.str(), "\n")) { 440 if (nest && x.starts_with("#")) 441 os << "#"; 442 os << x << "\n"; 443 } 444 } 445 446 static void emitBlock(ArrayRef<Attribute> attributes, StringRef inputFilename, 447 ArrayRef<AttrDef> attrDefs, ArrayRef<OpDocGroup> ops, 448 ArrayRef<Type> types, ArrayRef<TypeDef> typeDefs, 449 ArrayRef<EnumAttr> enums, raw_ostream &os) { 450 if (!ops.empty()) { 451 os << "## Operations\n\n"; 452 emitSourceLink(inputFilename, os); 453 for (const OpDocGroup &grouping : ops) { 454 bool nested = !grouping.summary.empty(); 455 maybeNest( 456 nested, 457 [&](raw_ostream &os) { 458 if (nested) { 459 os << "## " << StringRef(grouping.summary).trim() << "\n\n"; 460 emitDescription(grouping.description, os); 461 os << "\n\n"; 462 } 463 for (const Operator &op : grouping.ops) { 464 emitOpDoc(op, os); 465 } 466 }, 467 os); 468 } 469 } 470 471 if (!attributes.empty()) { 472 os << "## Attribute constraints\n\n"; 473 for (const Attribute &attr : attributes) 474 emitAttrDoc(attr, os); 475 } 476 477 if (!attrDefs.empty()) { 478 os << "## Attributes\n\n"; 479 for (const AttrDef &def : attrDefs) 480 emitAttrOrTypeDefDoc(def, os); 481 } 482 483 // TODO: Add link between use and def for types 484 if (!types.empty()) { 485 os << "## Type constraints\n\n"; 486 for (const Type &type : types) 487 emitTypeDoc(type, os); 488 } 489 490 if (!typeDefs.empty()) { 491 os << "## Types\n\n"; 492 for (const TypeDef &def : typeDefs) 493 emitAttrOrTypeDefDoc(def, os); 494 } 495 496 if (!enums.empty()) { 497 os << "## Enums\n\n"; 498 for (const EnumAttr &def : enums) 499 emitEnumDoc(def, os); 500 } 501 } 502 503 static void emitDialectDoc(const Dialect &dialect, StringRef inputFilename, 504 ArrayRef<Attribute> attributes, 505 ArrayRef<AttrDef> attrDefs, ArrayRef<OpDocGroup> ops, 506 ArrayRef<Type> types, ArrayRef<TypeDef> typeDefs, 507 ArrayRef<EnumAttr> enums, raw_ostream &os) { 508 os << "# '" << dialect.getName() << "' Dialect\n\n"; 509 emitIfNotEmpty(dialect.getSummary(), os); 510 emitIfNotEmpty(dialect.getDescription(), os); 511 512 // Generate a TOC marker except if description already contains one. 513 llvm::Regex r("^[[:space:]]*\\[TOC\\]$", llvm::Regex::RegexFlags::Newline); 514 if (!r.match(dialect.getDescription())) 515 os << "[TOC]\n\n"; 516 517 emitBlock(attributes, inputFilename, attrDefs, ops, types, typeDefs, enums, 518 os); 519 } 520 521 static bool emitDialectDoc(RecordKeeper &recordKeeper, raw_ostream &os) { 522 std::vector<Record *> dialectDefs = 523 recordKeeper.getAllDerivedDefinitionsIfDefined("Dialect"); 524 SmallVector<Dialect> dialects(dialectDefs.begin(), dialectDefs.end()); 525 std::optional<Dialect> dialect = findDialectToGenerate(dialects); 526 if (!dialect) 527 return true; 528 529 std::vector<Record *> opDefs = getRequestedOpDefinitions(recordKeeper); 530 std::vector<Record *> attrDefs = 531 recordKeeper.getAllDerivedDefinitionsIfDefined("DialectAttr"); 532 std::vector<Record *> typeDefs = 533 recordKeeper.getAllDerivedDefinitionsIfDefined("DialectType"); 534 std::vector<Record *> typeDefDefs = 535 recordKeeper.getAllDerivedDefinitionsIfDefined("TypeDef"); 536 std::vector<Record *> attrDefDefs = 537 recordKeeper.getAllDerivedDefinitionsIfDefined("AttrDef"); 538 std::vector<Record *> enumDefs = 539 recordKeeper.getAllDerivedDefinitionsIfDefined("EnumAttrInfo"); 540 541 std::vector<Attribute> dialectAttrs; 542 std::vector<AttrDef> dialectAttrDefs; 543 std::vector<OpDocGroup> dialectOps; 544 std::vector<Type> dialectTypes; 545 std::vector<TypeDef> dialectTypeDefs; 546 std::vector<EnumAttr> dialectEnums; 547 548 llvm::SmallDenseSet<Record *> seen; 549 auto addIfNotSeen = [&](llvm::Record *record, const auto &def, auto &vec) { 550 if (seen.insert(record).second) { 551 vec.push_back(def); 552 return true; 553 } 554 return false; 555 }; 556 auto addIfInDialect = [&](llvm::Record *record, const auto &def, auto &vec) { 557 return def.getDialect() == *dialect && addIfNotSeen(record, def, vec); 558 }; 559 560 SmallDenseMap<Record *, OpDocGroup> opDocGroup; 561 562 for (Record *def : attrDefDefs) 563 addIfInDialect(def, AttrDef(def), dialectAttrDefs); 564 for (Record *def : attrDefs) 565 addIfInDialect(def, Attribute(def), dialectAttrs); 566 for (Record *def : opDefs) { 567 if (Record *group = def->getValueAsOptionalDef("opDocGroup")) { 568 OpDocGroup &op = opDocGroup[group]; 569 addIfInDialect(def, Operator(def), op.ops); 570 } else { 571 OpDocGroup op; 572 op.ops.emplace_back(def); 573 addIfInDialect(def, op, dialectOps); 574 } 575 } 576 for (Record *rec : 577 recordKeeper.getAllDerivedDefinitionsIfDefined("OpDocGroup")) { 578 if (opDocGroup[rec].ops.empty()) 579 continue; 580 opDocGroup[rec].summary = rec->getValueAsString("summary"); 581 opDocGroup[rec].description = rec->getValueAsString("description"); 582 dialectOps.push_back(opDocGroup[rec]); 583 } 584 for (Record *def : typeDefDefs) 585 addIfInDialect(def, TypeDef(def), dialectTypeDefs); 586 for (Record *def : typeDefs) 587 addIfInDialect(def, Type(def), dialectTypes); 588 dialectEnums.reserve(enumDefs.size()); 589 for (Record *def : enumDefs) 590 addIfNotSeen(def, EnumAttr(def), dialectEnums); 591 592 // Sort alphabetically ignorning dialect for ops and section name for 593 // sections. 594 // TODO: The sorting order could be revised, currently attempting to sort of 595 // keep in alphabetical order. 596 std::sort(dialectOps.begin(), dialectOps.end(), 597 [](const OpDocGroup &lhs, const OpDocGroup &rhs) { 598 auto getDesc = [](const OpDocGroup &arg) -> StringRef { 599 if (!arg.summary.empty()) 600 return arg.summary; 601 return arg.ops.front().getDef().getValueAsString("opName"); 602 }; 603 return getDesc(lhs).compare_insensitive(getDesc(rhs)) < 0; 604 }); 605 606 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 607 emitDialectDoc(*dialect, recordKeeper.getInputFilename(), dialectAttrs, 608 dialectAttrDefs, dialectOps, dialectTypes, dialectTypeDefs, 609 dialectEnums, os); 610 return false; 611 } 612 613 //===----------------------------------------------------------------------===// 614 // Gen Registration 615 //===----------------------------------------------------------------------===// 616 617 static mlir::GenRegistration 618 genAttrRegister("gen-attrdef-doc", 619 "Generate dialect attribute documentation", 620 [](RecordKeeper &records, raw_ostream &os) { 621 emitAttrOrTypeDefDoc(records, os, "AttrDef"); 622 return false; 623 }); 624 625 static mlir::GenRegistration 626 genOpRegister("gen-op-doc", "Generate dialect documentation", 627 [](RecordKeeper &records, raw_ostream &os) { 628 emitOpDoc(records, os); 629 return false; 630 }); 631 632 static mlir::GenRegistration 633 genTypeRegister("gen-typedef-doc", "Generate dialect type documentation", 634 [](RecordKeeper &records, raw_ostream &os) { 635 emitAttrOrTypeDefDoc(records, os, "TypeDef"); 636 return false; 637 }); 638 639 static mlir::GenRegistration 640 genEnumRegister("gen-enum-doc", "Generate dialect enum documentation", 641 [](RecordKeeper &records, raw_ostream &os) { 642 emitEnumDoc(records, os); 643 return false; 644 }); 645 646 static mlir::GenRegistration 647 genRegister("gen-dialect-doc", "Generate dialect documentation", 648 [](RecordKeeper &records, raw_ostream &os) { 649 return emitDialectDoc(records, os); 650 }); 651