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/TableGen/GenInfo.h" 16 #include "mlir/TableGen/Operator.h" 17 #include "llvm/ADT/DenseMap.h" 18 #include "llvm/ADT/StringExtras.h" 19 #include "llvm/Support/FormatVariadic.h" 20 #include "llvm/Support/Signals.h" 21 #include "llvm/TableGen/Error.h" 22 #include "llvm/TableGen/Record.h" 23 #include "llvm/TableGen/TableGenBackend.h" 24 25 using namespace llvm; 26 using namespace mlir; 27 using namespace mlir::tblgen; 28 29 using mlir::tblgen::Operator; 30 31 // Emit the description by aligning the text to the left per line (e.g., 32 // removing the minimum indentation across the block). 33 // 34 // This expects that the description in the tablegen file is already formatted 35 // in a way the user wanted but has some additional indenting due to being 36 // nested in the op definition. 37 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) { 38 // Determine the minimum number of spaces in a line. 39 size_t min_indent = -1; 40 StringRef remaining = description; 41 while (!remaining.empty()) { 42 auto split = remaining.split('\n'); 43 size_t indent = split.first.find_first_not_of(" \t"); 44 if (indent != StringRef::npos) 45 min_indent = std::min(indent, min_indent); 46 remaining = split.second; 47 } 48 49 // Print out the description indented. 50 os << "\n"; 51 remaining = description; 52 bool printed = false; 53 while (!remaining.empty()) { 54 auto split = remaining.split('\n'); 55 if (split.second.empty()) { 56 // Skip last line with just spaces. 57 if (split.first.ltrim().empty()) 58 break; 59 } 60 // Print empty new line without spaces if line only has spaces, unless no 61 // text has been emitted before. 62 if (split.first.ltrim().empty()) { 63 if (printed) 64 os << "\n"; 65 } else { 66 os << split.first.substr(min_indent) << "\n"; 67 printed = true; 68 } 69 remaining = split.second; 70 } 71 } 72 73 // Emits `str` with trailing newline if not empty. 74 static void emitIfNotEmpty(StringRef str, raw_ostream &os) { 75 if (!str.empty()) { 76 emitDescription(str, os); 77 os << "\n"; 78 } 79 } 80 81 static void emitOpDocForDialect(const Dialect &dialect, 82 const std::vector<Operator> &ops, 83 const std::vector<Type> &types, 84 raw_ostream &os) { 85 os << "# Dialect '" << dialect.getName() << "' definition\n\n"; 86 emitIfNotEmpty(dialect.getSummary(), os); 87 emitIfNotEmpty(dialect.getDescription(), os); 88 89 // TODO(b/143543720) Generate TOC where extension is not supported. 90 os << "[TOC]\n\n"; 91 92 // TODO(antiagainst): Add link between use and def for types 93 if (!types.empty()) 94 os << "## Type definition\n\n"; 95 for (auto type : types) { 96 os << "### " << type.getDescription() << "\n"; 97 emitDescription(type.getTypeDescription(), os); 98 os << "\n"; 99 } 100 101 if (!ops.empty()) 102 os << "## Operation definition\n\n"; 103 for (auto op : ops) { 104 os << "### " << op.getOperationName() << " (" << op.getQualCppClassName() 105 << ")"; 106 107 // Emit summary & description of operator. 108 if (op.hasSummary()) 109 os << "\n" << op.getSummary() << "\n"; 110 os << "\n#### Description:\n\n"; 111 if (op.hasDescription()) 112 mlir::tblgen::emitDescription(op.getDescription(), os); 113 114 // Emit operands & type of operand. All operands are numbered, some may be 115 // named too. 116 os << "\n#### Operands:\n\n"; 117 for (const auto &operand : op.getOperands()) { 118 os << "1. "; 119 if (!operand.name.empty()) 120 os << "`" << operand.name << "`: "; 121 else 122 os << "«unnamed»: "; 123 os << operand.constraint.getDescription() << "\n"; 124 } 125 126 // Emit attributes. 127 // TODO: Attributes are only documented by TableGen name, with no further 128 // info. This should be improved. 129 os << "\n#### Attributes:\n\n"; 130 if (op.getNumAttributes() > 0) { 131 os << "| Attribute | MLIR Type | Description |\n" 132 << "| :-------: | :-------: | ----------- |\n"; 133 } 134 for (auto namedAttr : op.getAttributes()) { 135 os << "| `" << namedAttr.name << "` | `" 136 << namedAttr.attr.getStorageType() << "` | " 137 << namedAttr.attr.getDescription() << " attribute |\n"; 138 } 139 140 // Emit results. 141 os << "\n#### Results:\n\n"; 142 for (unsigned i = 0, e = op.getNumResults(); i < e; ++i) { 143 os << "1. "; 144 auto name = op.getResultName(i); 145 if (name.empty()) 146 os << "«unnamed»: "; 147 else 148 os << "`" << name << "`: "; 149 os << op.getResultTypeConstraint(i).getDescription() << "\n"; 150 } 151 152 os << "\n"; 153 } 154 } 155 156 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 157 const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op"); 158 const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType"); 159 160 std::map<Dialect, std::vector<Operator>> dialectOps; 161 std::map<Dialect, std::vector<Type>> dialectTypes; 162 for (auto *opDef : opDefs) { 163 Operator op(opDef); 164 dialectOps[op.getDialect()].push_back(op); 165 } 166 for (auto *typeDef : typeDefs) { 167 Type type(typeDef); 168 if (auto dialect = type.getDialect()) 169 dialectTypes[dialect].push_back(type); 170 } 171 172 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 173 for (auto dialectWithOps : dialectOps) 174 emitOpDocForDialect(dialectWithOps.first, dialectWithOps.second, 175 dialectTypes[dialectWithOps.first], os); 176 } 177 178 static mlir::GenRegistration 179 genRegister("gen-op-doc", "Generate operation documentation", 180 [](const RecordKeeper &records, raw_ostream &os) { 181 emitOpDoc(records, os); 182 return false; 183 }); 184