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 using namespace llvm; 38 using namespace mlir; 39 using namespace mlir::tblgen; 40 using mlir::tblgen::Operator; 41 42 //===----------------------------------------------------------------------===// 43 // Commandline Options 44 //===----------------------------------------------------------------------===// 45 static cl::OptionCategory 46 docCat("Options for -gen-(attrdef|typedef|enum|op|dialect)-doc"); 47 cl::opt<std::string> 48 stripPrefix("strip-prefix", 49 cl::desc("Strip prefix of the fully qualified names"), 50 cl::init("::mlir::"), cl::cat(docCat)); 51 cl::opt<bool> allowHugoSpecificFeatures( 52 "allow-hugo-specific-features", 53 cl::desc("Allows using features specific to Hugo"), cl::init(false), 54 cl::cat(docCat)); 55 56 void mlir::tblgen::emitSummary(StringRef summary, raw_ostream &os) { 57 if (!summary.empty()) { 58 StringRef trimmed = summary.trim(); 59 char first = std::toupper(trimmed.front()); 60 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 raw_string_ostream os(effectStr); 156 os << effectName << "{"; 157 auto list = trait.getDef().getValueAsListOfDefs("effects"); 158 interleaveComma(list, os, [&](const 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(effectStr)); 166 name.append(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 interleaveComma(traits, os << "\nTraits: "); 176 os << "\n"; 177 } 178 if (!interfaces.empty()) { 179 interleaveComma(interfaces, os << "\nInterfaces: "); 180 os << "\n"; 181 } 182 if (!effects.empty()) { 183 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 << 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(const 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 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 << 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(const RecordKeeper &recordKeeper, 375 raw_ostream &os, StringRef recordTypeName) { 376 auto defs = recordKeeper.getAllDerivedDefinitions(recordTypeName); 377 378 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 379 for (const Record *def : defs) 380 emitAttrOrTypeDefDoc(AttrOrTypeDef(def), os); 381 } 382 383 //===----------------------------------------------------------------------===// 384 // Enum Documentation 385 //===----------------------------------------------------------------------===// 386 387 static void emitEnumDoc(const EnumAttr &def, raw_ostream &os) { 388 os << formatv("### {0}\n", def.getEnumClassName()); 389 390 // Emit the summary if present. 391 if (!def.getSummary().empty()) 392 os << "\n" << def.getSummary() << "\n"; 393 394 // Emit case documentation. 395 std::vector<EnumAttrCase> cases = def.getAllCases(); 396 os << "\n#### Cases:\n\n"; 397 os << "| Symbol | Value | String |\n" 398 << "| :----: | :---: | ------ |\n"; 399 for (const auto &it : cases) { 400 os << "| " << it.getSymbol() << " | `" << it.getValue() << "` | " 401 << it.getStr() << " |\n"; 402 } 403 404 os << "\n"; 405 } 406 407 static void emitEnumDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 408 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 409 for (const Record *def : recordKeeper.getAllDerivedDefinitions("EnumAttr")) 410 emitEnumDoc(EnumAttr(def), os); 411 } 412 413 //===----------------------------------------------------------------------===// 414 // Dialect Documentation 415 //===----------------------------------------------------------------------===// 416 417 struct OpDocGroup { 418 const Dialect &getDialect() const { return ops.front().getDialect(); } 419 420 // Returns the summary description of the section. 421 std::string summary = ""; 422 423 // Returns the description of the section. 424 StringRef description = ""; 425 426 // Instances inside the section. 427 std::vector<Operator> ops; 428 }; 429 430 static void maybeNest(bool nest, llvm::function_ref<void(raw_ostream &os)> fn, 431 raw_ostream &os) { 432 std::string str; 433 raw_string_ostream ss(str); 434 fn(ss); 435 for (StringRef x : llvm::split(str, "\n")) { 436 if (nest && x.starts_with("#")) 437 os << "#"; 438 os << x << "\n"; 439 } 440 } 441 442 static void emitBlock(ArrayRef<Attribute> attributes, StringRef inputFilename, 443 ArrayRef<AttrDef> attrDefs, ArrayRef<OpDocGroup> ops, 444 ArrayRef<Type> types, ArrayRef<TypeDef> typeDefs, 445 ArrayRef<EnumAttr> enums, raw_ostream &os) { 446 if (!ops.empty()) { 447 os << "## Operations\n\n"; 448 emitSourceLink(inputFilename, os); 449 for (const OpDocGroup &grouping : ops) { 450 bool nested = !grouping.summary.empty(); 451 maybeNest( 452 nested, 453 [&](raw_ostream &os) { 454 if (nested) { 455 os << "## " << StringRef(grouping.summary).trim() << "\n\n"; 456 emitDescription(grouping.description, os); 457 os << "\n\n"; 458 } 459 for (const Operator &op : grouping.ops) { 460 emitOpDoc(op, os); 461 } 462 }, 463 os); 464 } 465 } 466 467 if (!attributes.empty()) { 468 os << "## Attribute constraints\n\n"; 469 for (const Attribute &attr : attributes) 470 emitAttrDoc(attr, os); 471 } 472 473 if (!attrDefs.empty()) { 474 os << "## Attributes\n\n"; 475 for (const AttrDef &def : attrDefs) 476 emitAttrOrTypeDefDoc(def, os); 477 } 478 479 // TODO: Add link between use and def for types 480 if (!types.empty()) { 481 os << "## Type constraints\n\n"; 482 for (const Type &type : types) 483 emitTypeDoc(type, os); 484 } 485 486 if (!typeDefs.empty()) { 487 os << "## Types\n\n"; 488 for (const TypeDef &def : typeDefs) 489 emitAttrOrTypeDefDoc(def, os); 490 } 491 492 if (!enums.empty()) { 493 os << "## Enums\n\n"; 494 for (const EnumAttr &def : enums) 495 emitEnumDoc(def, os); 496 } 497 } 498 499 static void emitDialectDoc(const Dialect &dialect, StringRef inputFilename, 500 ArrayRef<Attribute> attributes, 501 ArrayRef<AttrDef> attrDefs, ArrayRef<OpDocGroup> ops, 502 ArrayRef<Type> types, ArrayRef<TypeDef> typeDefs, 503 ArrayRef<EnumAttr> enums, raw_ostream &os) { 504 os << "# '" << dialect.getName() << "' Dialect\n\n"; 505 emitIfNotEmpty(dialect.getSummary(), os); 506 emitIfNotEmpty(dialect.getDescription(), os); 507 508 // Generate a TOC marker except if description already contains one. 509 Regex r("^[[:space:]]*\\[TOC\\]$", Regex::RegexFlags::Newline); 510 if (!r.match(dialect.getDescription())) 511 os << "[TOC]\n\n"; 512 513 emitBlock(attributes, inputFilename, attrDefs, ops, types, typeDefs, enums, 514 os); 515 } 516 517 static bool emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 518 auto dialectDefs = recordKeeper.getAllDerivedDefinitionsIfDefined("Dialect"); 519 SmallVector<Dialect> dialects(dialectDefs.begin(), dialectDefs.end()); 520 std::optional<Dialect> dialect = findDialectToGenerate(dialects); 521 if (!dialect) 522 return true; 523 524 std::vector<const Record *> opDefs = getRequestedOpDefinitions(recordKeeper); 525 auto attrDefs = recordKeeper.getAllDerivedDefinitionsIfDefined("DialectAttr"); 526 auto typeDefs = recordKeeper.getAllDerivedDefinitionsIfDefined("DialectType"); 527 auto typeDefDefs = recordKeeper.getAllDerivedDefinitionsIfDefined("TypeDef"); 528 auto attrDefDefs = recordKeeper.getAllDerivedDefinitionsIfDefined("AttrDef"); 529 auto enumDefs = 530 recordKeeper.getAllDerivedDefinitionsIfDefined("EnumAttrInfo"); 531 532 std::vector<Attribute> dialectAttrs; 533 std::vector<AttrDef> dialectAttrDefs; 534 std::vector<OpDocGroup> dialectOps; 535 std::vector<Type> dialectTypes; 536 std::vector<TypeDef> dialectTypeDefs; 537 std::vector<EnumAttr> dialectEnums; 538 539 SmallDenseSet<const Record *> seen; 540 auto addIfNotSeen = [&](const Record *record, const auto &def, auto &vec) { 541 if (seen.insert(record).second) { 542 vec.push_back(def); 543 return true; 544 } 545 return false; 546 }; 547 auto addIfInDialect = [&](const Record *record, const auto &def, auto &vec) { 548 return def.getDialect() == *dialect && addIfNotSeen(record, def, vec); 549 }; 550 551 SmallDenseMap<const Record *, OpDocGroup> opDocGroup; 552 553 for (const Record *def : attrDefDefs) 554 addIfInDialect(def, AttrDef(def), dialectAttrDefs); 555 for (const Record *def : attrDefs) 556 addIfInDialect(def, Attribute(def), dialectAttrs); 557 for (const Record *def : opDefs) { 558 if (const Record *group = def->getValueAsOptionalDef("opDocGroup")) { 559 OpDocGroup &op = opDocGroup[group]; 560 addIfInDialect(def, Operator(def), op.ops); 561 } else { 562 OpDocGroup op; 563 op.ops.emplace_back(def); 564 addIfInDialect(def, op, dialectOps); 565 } 566 } 567 for (const Record *rec : 568 recordKeeper.getAllDerivedDefinitionsIfDefined("OpDocGroup")) { 569 if (opDocGroup[rec].ops.empty()) 570 continue; 571 opDocGroup[rec].summary = rec->getValueAsString("summary"); 572 opDocGroup[rec].description = rec->getValueAsString("description"); 573 dialectOps.push_back(opDocGroup[rec]); 574 } 575 for (const Record *def : typeDefDefs) 576 addIfInDialect(def, TypeDef(def), dialectTypeDefs); 577 for (const Record *def : typeDefs) 578 addIfInDialect(def, Type(def), dialectTypes); 579 dialectEnums.reserve(enumDefs.size()); 580 for (const Record *def : enumDefs) 581 addIfNotSeen(def, EnumAttr(def), dialectEnums); 582 583 // Sort alphabetically ignorning dialect for ops and section name for 584 // sections. 585 // TODO: The sorting order could be revised, currently attempting to sort of 586 // keep in alphabetical order. 587 std::sort(dialectOps.begin(), dialectOps.end(), 588 [](const OpDocGroup &lhs, const OpDocGroup &rhs) { 589 auto getDesc = [](const OpDocGroup &arg) -> StringRef { 590 if (!arg.summary.empty()) 591 return arg.summary; 592 return arg.ops.front().getDef().getValueAsString("opName"); 593 }; 594 return getDesc(lhs).compare_insensitive(getDesc(rhs)) < 0; 595 }); 596 597 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 598 emitDialectDoc(*dialect, recordKeeper.getInputFilename(), dialectAttrs, 599 dialectAttrDefs, dialectOps, dialectTypes, dialectTypeDefs, 600 dialectEnums, os); 601 return false; 602 } 603 604 //===----------------------------------------------------------------------===// 605 // Gen Registration 606 //===----------------------------------------------------------------------===// 607 608 static mlir::GenRegistration 609 genAttrRegister("gen-attrdef-doc", 610 "Generate dialect attribute documentation", 611 [](const RecordKeeper &records, raw_ostream &os) { 612 emitAttrOrTypeDefDoc(records, os, "AttrDef"); 613 return false; 614 }); 615 616 static mlir::GenRegistration 617 genOpRegister("gen-op-doc", "Generate dialect documentation", 618 [](const RecordKeeper &records, raw_ostream &os) { 619 emitOpDoc(records, os); 620 return false; 621 }); 622 623 static mlir::GenRegistration 624 genTypeRegister("gen-typedef-doc", "Generate dialect type documentation", 625 [](const RecordKeeper &records, raw_ostream &os) { 626 emitAttrOrTypeDefDoc(records, os, "TypeDef"); 627 return false; 628 }); 629 630 static mlir::GenRegistration 631 genEnumRegister("gen-enum-doc", "Generate dialect enum documentation", 632 [](const RecordKeeper &records, raw_ostream &os) { 633 emitEnumDoc(records, os); 634 return false; 635 }); 636 637 static mlir::GenRegistration 638 genRegister("gen-dialect-doc", "Generate dialect documentation", 639 [](const RecordKeeper &records, raw_ostream &os) { 640 return emitDialectDoc(records, os); 641 }); 642