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 /// Emit the given named constraint. 82 template <typename T> 83 static void emitNamedConstraint(const T &it, raw_ostream &os) { 84 if (!it.name.empty()) 85 os << "`" << it.name << "`"; 86 else 87 os << "«unnamed»"; 88 os << " | " << it.constraint.getDescription() << "\n"; 89 } 90 91 //===----------------------------------------------------------------------===// 92 // Operation Documentation 93 //===----------------------------------------------------------------------===// 94 95 /// Emit the assembly format of an operation. 96 static void emitAssemblyFormat(StringRef opName, StringRef format, 97 raw_ostream &os) { 98 os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` "; 99 100 // Print the assembly format aligned. 101 unsigned indent = strlen("operation ::= "); 102 std::pair<StringRef, StringRef> split = format.split('\n'); 103 os << split.first.trim() << "\n"; 104 do { 105 split = split.second.split('\n'); 106 StringRef formatChunk = split.first.trim(); 107 if (!formatChunk.empty()) 108 os.indent(indent) << formatChunk << "\n"; 109 } while (!split.second.empty()); 110 os << "```\n\n"; 111 } 112 113 static void emitOpDoc(Operator op, raw_ostream &os) { 114 os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(), 115 op.getQualCppClassName()); 116 117 // Emit the summary, syntax, and description if present. 118 if (op.hasSummary()) 119 os << "\n" << op.getSummary() << "\n"; 120 if (op.hasAssemblyFormat()) 121 emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(), 122 os); 123 if (op.hasDescription()) 124 mlir::tblgen::emitDescription(op.getDescription(), os); 125 126 // Emit attributes. 127 if (op.getNumAttributes() != 0) { 128 // TODO: Attributes are only documented by TableGen name, with no further 129 // info. This should be improved. 130 os << "\n#### Attributes:\n\n"; 131 os << "| Attribute | MLIR Type | Description |\n" 132 << "| :-------: | :-------: | ----------- |\n"; 133 for (const auto &it : op.getAttributes()) { 134 StringRef storageType = it.attr.getStorageType(); 135 os << "`" << it.name << "` | " << storageType << " | " 136 << it.attr.getDescription() << "\n"; 137 } 138 } 139 140 // Emit each of the operands. 141 if (op.getNumOperands() != 0) { 142 os << "\n#### Operands:\n\n"; 143 os << "| Operand | Description |\n" 144 << "| :-----: | ----------- |\n"; 145 for (const auto &it : op.getOperands()) 146 emitNamedConstraint(it, os); 147 } 148 149 // Emit results. 150 if (op.getNumResults() != 0) { 151 os << "\n#### Results:\n\n"; 152 os << "| Result | Description |\n" 153 << "| :----: | ----------- |\n"; 154 for (const auto &it : op.getResults()) 155 emitNamedConstraint(it, os); 156 } 157 158 // Emit successors. 159 if (op.getNumSuccessors() != 0) { 160 os << "\n#### Successors:\n\n"; 161 os << "| Successor | Description |\n" 162 << "| :-------: | ----------- |\n"; 163 for (const auto &it : op.getSuccessors()) 164 emitNamedConstraint(it, os); 165 } 166 167 os << "\n"; 168 } 169 170 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 171 auto opDefs = recordKeeper.getAllDerivedDefinitions("Op"); 172 173 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 174 for (const llvm::Record *opDef : opDefs) 175 emitOpDoc(Operator(opDef), os); 176 } 177 178 //===----------------------------------------------------------------------===// 179 // Type Documentation 180 //===----------------------------------------------------------------------===// 181 182 static void emitTypeDoc(const Type &type, raw_ostream &os) { 183 os << "### " << type.getDescription() << "\n"; 184 emitDescription(type.getTypeDescription(), os); 185 os << "\n"; 186 } 187 188 //===----------------------------------------------------------------------===// 189 // Dialect Documentation 190 //===----------------------------------------------------------------------===// 191 192 static void emitDialectDoc(const Dialect &dialect, ArrayRef<Operator> ops, 193 ArrayRef<Type> types, raw_ostream &os) { 194 os << "# '" << dialect.getName() << "' Dialect\n\n"; 195 emitIfNotEmpty(dialect.getSummary(), os); 196 emitIfNotEmpty(dialect.getDescription(), os); 197 198 os << "[TOC]\n\n"; 199 200 // TODO: Add link between use and def for types 201 if (!types.empty()) { 202 os << "## Type definition\n\n"; 203 for (const Type &type : types) 204 emitTypeDoc(type, os); 205 } 206 207 if (!ops.empty()) { 208 os << "## Operation definition\n\n"; 209 for (const Operator &op : ops) 210 emitOpDoc(op, os); 211 } 212 } 213 214 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 215 const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op"); 216 const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType"); 217 218 std::map<Dialect, std::vector<Operator>> dialectOps; 219 std::map<Dialect, std::vector<Type>> dialectTypes; 220 for (auto *opDef : opDefs) { 221 Operator op(opDef); 222 dialectOps[op.getDialect()].push_back(op); 223 } 224 for (auto *typeDef : typeDefs) { 225 Type type(typeDef); 226 if (auto dialect = type.getDialect()) 227 dialectTypes[dialect].push_back(type); 228 } 229 230 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 231 for (auto dialectWithOps : dialectOps) 232 emitDialectDoc(dialectWithOps.first, dialectWithOps.second, 233 dialectTypes[dialectWithOps.first], os); 234 } 235 236 //===----------------------------------------------------------------------===// 237 // Gen Registration 238 //===----------------------------------------------------------------------===// 239 240 static mlir::GenRegistration 241 genOpRegister("gen-op-doc", "Generate dialect documentation", 242 [](const RecordKeeper &records, raw_ostream &os) { 243 emitOpDoc(records, os); 244 return false; 245 }); 246 247 static mlir::GenRegistration 248 genRegister("gen-dialect-doc", "Generate dialect documentation", 249 [](const RecordKeeper &records, raw_ostream &os) { 250 emitDialectDoc(records, os); 251 return false; 252 }); 253