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/GenInfo.h" 17 #include "mlir/TableGen/Operator.h" 18 #include "llvm/ADT/DenseMap.h" 19 #include "llvm/ADT/StringExtras.h" 20 #include "llvm/Support/FormatVariadic.h" 21 #include "llvm/Support/Signals.h" 22 #include "llvm/TableGen/Error.h" 23 #include "llvm/TableGen/Record.h" 24 #include "llvm/TableGen/TableGenBackend.h" 25 26 using namespace llvm; 27 using namespace mlir; 28 using namespace mlir::tblgen; 29 30 using mlir::tblgen::Operator; 31 32 // Emit the description by aligning the text to the left per line (e.g., 33 // removing the minimum indentation across the block). 34 // 35 // This expects that the description in the tablegen file is already formatted 36 // in a way the user wanted but has some additional indenting due to being 37 // nested in the op definition. 38 void mlir::tblgen::emitDescription(StringRef description, raw_ostream &os) { 39 raw_indented_ostream ros(os); 40 ros.reindent(description.rtrim(" \t")); 41 } 42 43 // Emits `str` with trailing newline if not empty. 44 static void emitIfNotEmpty(StringRef str, raw_ostream &os) { 45 if (!str.empty()) { 46 emitDescription(str, os); 47 os << "\n"; 48 } 49 } 50 51 /// Emit the given named constraint. 52 template <typename T> 53 static void emitNamedConstraint(const T &it, raw_ostream &os) { 54 if (!it.name.empty()) 55 os << "`" << it.name << "`"; 56 else 57 os << "«unnamed»"; 58 os << " | " << it.constraint.getDescription() << "\n"; 59 } 60 61 //===----------------------------------------------------------------------===// 62 // Operation Documentation 63 //===----------------------------------------------------------------------===// 64 65 /// Emit the assembly format of an operation. 66 static void emitAssemblyFormat(StringRef opName, StringRef format, 67 raw_ostream &os) { 68 os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` "; 69 70 // Print the assembly format aligned. 71 unsigned indent = strlen("operation ::= "); 72 std::pair<StringRef, StringRef> split = format.split('\n'); 73 os << split.first.trim() << "\n"; 74 do { 75 split = split.second.split('\n'); 76 StringRef formatChunk = split.first.trim(); 77 if (!formatChunk.empty()) 78 os.indent(indent) << formatChunk << "\n"; 79 } while (!split.second.empty()); 80 os << "```\n\n"; 81 } 82 83 static void emitOpDoc(Operator op, raw_ostream &os) { 84 os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(), 85 op.getQualCppClassName()); 86 87 // Emit the summary, syntax, and description if present. 88 if (op.hasSummary()) 89 os << "\n" << op.getSummary() << "\n\n"; 90 if (op.hasAssemblyFormat()) 91 emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(), 92 os); 93 if (op.hasDescription()) 94 mlir::tblgen::emitDescription(op.getDescription(), os); 95 96 // Emit attributes. 97 if (op.getNumAttributes() != 0) { 98 // TODO: Attributes are only documented by TableGen name, with no further 99 // info. This should be improved. 100 os << "\n#### Attributes:\n\n"; 101 os << "| Attribute | MLIR Type | Description |\n" 102 << "| :-------: | :-------: | ----------- |\n"; 103 for (const auto &it : op.getAttributes()) { 104 StringRef storageType = it.attr.getStorageType(); 105 os << "`" << it.name << "` | " << storageType << " | " 106 << it.attr.getDescription() << "\n"; 107 } 108 } 109 110 // Emit each of the operands. 111 if (op.getNumOperands() != 0) { 112 os << "\n#### Operands:\n\n"; 113 os << "| Operand | Description |\n" 114 << "| :-----: | ----------- |\n"; 115 for (const auto &it : op.getOperands()) 116 emitNamedConstraint(it, os); 117 } 118 119 // Emit results. 120 if (op.getNumResults() != 0) { 121 os << "\n#### Results:\n\n"; 122 os << "| Result | Description |\n" 123 << "| :----: | ----------- |\n"; 124 for (const auto &it : op.getResults()) 125 emitNamedConstraint(it, os); 126 } 127 128 // Emit successors. 129 if (op.getNumSuccessors() != 0) { 130 os << "\n#### Successors:\n\n"; 131 os << "| Successor | Description |\n" 132 << "| :-------: | ----------- |\n"; 133 for (const auto &it : op.getSuccessors()) 134 emitNamedConstraint(it, os); 135 } 136 137 os << "\n"; 138 } 139 140 static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 141 auto opDefs = recordKeeper.getAllDerivedDefinitions("Op"); 142 143 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 144 for (const llvm::Record *opDef : opDefs) 145 emitOpDoc(Operator(opDef), os); 146 } 147 148 //===----------------------------------------------------------------------===// 149 // Type Documentation 150 //===----------------------------------------------------------------------===// 151 152 static void emitTypeDoc(const Type &type, raw_ostream &os) { 153 os << "### " << type.getDescription() << "\n"; 154 emitDescription(type.getTypeDescription(), os); 155 os << "\n"; 156 } 157 158 //===----------------------------------------------------------------------===// 159 // Dialect Documentation 160 //===----------------------------------------------------------------------===// 161 162 static void emitDialectDoc(const Dialect &dialect, ArrayRef<Operator> ops, 163 ArrayRef<Type> types, raw_ostream &os) { 164 os << "# '" << dialect.getName() << "' Dialect\n\n"; 165 emitIfNotEmpty(dialect.getSummary(), os); 166 emitIfNotEmpty(dialect.getDescription(), os); 167 168 os << "[TOC]\n\n"; 169 170 // TODO: Add link between use and def for types 171 if (!types.empty()) { 172 os << "## Type definition\n\n"; 173 for (const Type &type : types) 174 emitTypeDoc(type, os); 175 } 176 177 if (!ops.empty()) { 178 os << "## Operation definition\n\n"; 179 for (const Operator &op : ops) 180 emitOpDoc(op, os); 181 } 182 } 183 184 static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { 185 const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op"); 186 const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType"); 187 188 std::map<Dialect, std::vector<Operator>> dialectOps; 189 std::map<Dialect, std::vector<Type>> dialectTypes; 190 for (auto *opDef : opDefs) { 191 Operator op(opDef); 192 dialectOps[op.getDialect()].push_back(op); 193 } 194 for (auto *typeDef : typeDefs) { 195 Type type(typeDef); 196 if (auto dialect = type.getDialect()) 197 dialectTypes[dialect].push_back(type); 198 } 199 200 os << "<!-- Autogenerated by mlir-tblgen; don't manually edit -->\n"; 201 for (const auto &dialectWithOps : dialectOps) 202 emitDialectDoc(dialectWithOps.first, dialectWithOps.second, 203 dialectTypes[dialectWithOps.first], os); 204 } 205 206 //===----------------------------------------------------------------------===// 207 // Gen Registration 208 //===----------------------------------------------------------------------===// 209 210 static mlir::GenRegistration 211 genOpRegister("gen-op-doc", "Generate dialect documentation", 212 [](const RecordKeeper &records, raw_ostream &os) { 213 emitOpDoc(records, os); 214 return false; 215 }); 216 217 static mlir::GenRegistration 218 genRegister("gen-dialect-doc", "Generate dialect documentation", 219 [](const RecordKeeper &records, raw_ostream &os) { 220 emitDialectDoc(records, os); 221 return false; 222 }); 223