diff --git a/mlir/cmake/modules/AddMLIR.cmake b/mlir/cmake/modules/AddMLIR.cmake --- a/mlir/cmake/modules/AddMLIR.cmake +++ b/mlir/cmake/modules/AddMLIR.cmake @@ -32,26 +32,28 @@ endfunction(whole_archive_link) # Declare a dialect in the include directory -function(add_mlir_dialect dialect dialect_namespace dialect_doc_filename) +function(add_mlir_dialect dialect dialect_namespace) set(LLVM_TARGET_DEFINITIONS ${dialect}.td) mlir_tablegen(${dialect}.h.inc -gen-op-decls) mlir_tablegen(${dialect}.cpp.inc -gen-op-defs) mlir_tablegen(${dialect}Dialect.h.inc -gen-dialect-decls -dialect=${dialect_namespace}) add_public_tablegen_target(MLIR${dialect}IncGen) add_dependencies(mlir-headers MLIR${dialect}IncGen) +endfunction() - # Generate Dialect Documentation - set(LLVM_TARGET_DEFINITIONS ${dialect_doc_filename}.td) - tablegen(MLIR ${dialect_doc_filename}.md -gen-op-doc "-I${MLIR_MAIN_SRC_DIR}" "-I${MLIR_INCLUDE_DIR}") - set(GEN_DOC_FILE ${MLIR_BINARY_DIR}/docs/Dialects/${dialect_doc_filename}.md) +# Generate Documentation +function(add_mlir_doc doc_filename command output_file output_directory) + set(LLVM_TARGET_DEFINITIONS ${doc_filename}.td) + tablegen(MLIR ${output_file}.md ${command} "-I${MLIR_MAIN_SRC_DIR}" "-I${MLIR_INCLUDE_DIR}") + set(GEN_DOC_FILE ${MLIR_BINARY_DIR}/docs/${output_directory}${output_file}.md) add_custom_command( OUTPUT ${GEN_DOC_FILE} COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_BINARY_DIR}/${dialect_doc_filename}.md + ${CMAKE_CURRENT_BINARY_DIR}/${output_file}.md ${GEN_DOC_FILE} - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${dialect_doc_filename}.md) - add_custom_target(${dialect_doc_filename}DocGen DEPENDS ${GEN_DOC_FILE}) - add_dependencies(mlir-doc ${dialect_doc_filename}DocGen) + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${output_file}.md) + add_custom_target(${output_file}DocGen DEPENDS ${GEN_DOC_FILE}) + add_dependencies(mlir-doc ${output_file}DocGen) endfunction() # Declare a library which can be compiled in libMLIR.so diff --git a/mlir/docs/CreatingADialect.md b/mlir/docs/CreatingADialect.md --- a/mlir/docs/CreatingADialect.md +++ b/mlir/docs/CreatingADialect.md @@ -39,7 +39,8 @@ ```cmake -add_mlir_dialect(FooOps foo FooOps) +add_mlir_dialect(FooOps foo) +add_mlir_doc(FooOps -gen-dialect-doc FooDialect Dialects/) ``` diff --git a/mlir/docs/Dialects/Vector.md b/mlir/docs/Dialects/Vector.md --- a/mlir/docs/Dialects/Vector.md +++ b/mlir/docs/Dialects/Vector.md @@ -1,5 +1,7 @@ # Vector Dialect +[TOC] + MLIR supports multi-dimensional `vector` types and custom operations on those types. A generic, retargetable, higher-order ``vector`` type (`n-D` with `n > 1`) is a structured type, that carries semantic information useful for @@ -488,6 +490,6 @@ The use of special intrinsics in a `1-D` LLVM world is still available thanks to an explicit `vector.cast` op. +## Operations -### Operations - +[include "Dialects/VectorOps.md"] diff --git a/mlir/docs/OpDefinitions.md b/mlir/docs/OpDefinitions.md --- a/mlir/docs/OpDefinitions.md +++ b/mlir/docs/OpDefinitions.md @@ -1222,7 +1222,7 @@ # To see op C++ class definition mlir-tblgen --gen-op-defs -I /path/to/mlir/include /path/to/input/td/file # To see op documentation -mlir-tblgen --gen-op-doc -I /path/to/mlir/include /path/to/input/td/file +mlir-tblgen --gen-dialect-doc -I /path/to/mlir/include /path/to/input/td/file # To see op interface C++ class declaration mlir-tblgen --gen-op-interface-decls -I /path/to/mlir/include /path/to/input/td/file @@ -1232,7 +1232,6 @@ mlir-tblgen --gen-op-interface-doc -I /path/to/mlir/include /path/to/input/td/file ``` - ## Appendix ### Requirements and existing mechanisms analysis diff --git a/mlir/include/mlir/Dialect/Affine/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Affine/IR/CMakeLists.txt --- a/mlir/include/mlir/Dialect/Affine/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Affine/IR/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(AffineOps affine AffineOps) +add_mlir_dialect(AffineOps affine) +add_mlir_doc(AffineOps -gen-dialect-doc AffineDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/FxpMathOps/CMakeLists.txt b/mlir/include/mlir/Dialect/FxpMathOps/CMakeLists.txt --- a/mlir/include/mlir/Dialect/FxpMathOps/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/FxpMathOps/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(FxpMathOps fxpmath FxpMathOps) +add_mlir_dialect(FxpMathOps fxpmath) +add_mlir_doc(FxpMathOps -gen-dialect-doc FxpMathDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/GPU/CMakeLists.txt b/mlir/include/mlir/Dialect/GPU/CMakeLists.txt --- a/mlir/include/mlir/Dialect/GPU/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/GPU/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(GPUOps gpu GPUOps) +add_mlir_dialect(GPUOps gpu) +add_mlir_doc(GPUOps -gen-dialect-doc GPUDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/LLVMIR/CMakeLists.txt b/mlir/include/mlir/Dialect/LLVMIR/CMakeLists.txt --- a/mlir/include/mlir/Dialect/LLVMIR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/LLVMIR/CMakeLists.txt @@ -6,8 +6,10 @@ mlir_tablegen(LLVMOpsEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(MLIRLLVMOpsIncGen) -add_mlir_dialect(NVVMOps nvvm NVVMOps) -add_mlir_dialect(ROCDLOps rocdl ROCDLOps) +add_mlir_dialect(NVVMOps nvvm) +add_mlir_doc(NVVMOps -gen-dialect-doc NVVMDialect Dialects/) +add_mlir_dialect(ROCDLOps rocdl) +add_mlir_doc(ROCDLOps -gen-dialect-doc ROCDLDialect Dialects/) set(LLVM_TARGET_DEFINITIONS LLVMOps.td) mlir_tablegen(LLVMConversions.inc -gen-llvmir-conversions) diff --git a/mlir/include/mlir/Dialect/Linalg/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Linalg/IR/CMakeLists.txt --- a/mlir/include/mlir/Dialect/Linalg/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Linalg/IR/CMakeLists.txt @@ -1,4 +1,5 @@ -add_mlir_dialect(LinalgOps linalg LinalgDoc) +add_mlir_dialect(LinalgOps linalg) +add_mlir_doc(LinalgDoc -gen-dialect-doc LinalgDialect Dialects/) set(LLVM_TARGET_DEFINITIONS LinalgStructuredOps.td) mlir_tablegen(LinalgStructuredOps.h.inc -gen-op-decls) mlir_tablegen(LinalgStructuredOps.cpp.inc -gen-op-defs) diff --git a/mlir/include/mlir/Dialect/LoopOps/CMakeLists.txt b/mlir/include/mlir/Dialect/LoopOps/CMakeLists.txt --- a/mlir/include/mlir/Dialect/LoopOps/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/LoopOps/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(LoopOps loop LoopOps) +add_mlir_dialect(LoopOps loop) +add_mlir_doc(LoopOps -gen-dialect-doc LoopDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt b/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt --- a/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(OpenMPOps omp OpenMPOps) +add_mlir_dialect(OpenMPOps omp) +add_mlir_doc(OpenMPOps -gen-dialect-doc OpenMPDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/Quant/CMakeLists.txt b/mlir/include/mlir/Dialect/Quant/CMakeLists.txt --- a/mlir/include/mlir/Dialect/Quant/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Quant/CMakeLists.txt @@ -1 +1,2 @@ -add_mlir_dialect(QuantOps quant QuantOps) +add_mlir_dialect(QuantOps quant) +add_mlir_doc(QuantOps -gen-dialect-doc QuantDialect Dialects/) diff --git a/mlir/include/mlir/Dialect/SPIRV/CMakeLists.txt b/mlir/include/mlir/Dialect/SPIRV/CMakeLists.txt --- a/mlir/include/mlir/Dialect/SPIRV/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/SPIRV/CMakeLists.txt @@ -1,4 +1,5 @@ -add_mlir_dialect(SPIRVOps spv SPIRVOps) +add_mlir_dialect(SPIRVOps spv) +add_mlir_doc(SPIRVOps -gen-dialect-doc SPIRVDialect Dialects/) set(LLVM_TARGET_DEFINITIONS SPIRVBase.td) mlir_tablegen(SPIRVEnums.h.inc -gen-enum-decls) diff --git a/mlir/include/mlir/Dialect/Vector/CMakeLists.txt b/mlir/include/mlir/Dialect/Vector/CMakeLists.txt --- a/mlir/include/mlir/Dialect/Vector/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Vector/CMakeLists.txt @@ -1,4 +1,5 @@ -add_mlir_dialect(VectorOps vector VectorOps) +add_mlir_dialect(VectorOps vector) +add_mlir_doc(VectorOps -gen-op-doc VectorOps Dialects/) set(LLVM_TARGET_DEFINITIONS VectorTransformPatterns.td) mlir_tablegen(VectorTransformPatterns.h.inc -gen-rewriters) diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h --- a/mlir/include/mlir/TableGen/Operator.h +++ b/mlir/include/mlir/TableGen/Operator.h @@ -198,6 +198,10 @@ bool hasSummary() const; StringRef getSummary() const; + // Query functions for the assembly format of the operator. + bool hasAssemblyFormat() const; + StringRef getAssemblyFormat() const; + // Returns this op's extra class declaration code. StringRef getExtraClassDeclaration() const; diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp --- a/mlir/lib/TableGen/Operator.cpp +++ b/mlir/lib/TableGen/Operator.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "mlir/TableGen/Operator.h" +#include "mlir/ADT/TypeSwitch.h" #include "mlir/TableGen/OpTrait.h" #include "mlir/TableGen/Predicate.h" #include "mlir/TableGen/Type.h" @@ -411,6 +412,17 @@ return def.getValueAsString("summary"); } +bool tblgen::Operator::hasAssemblyFormat() const { + auto *valueInit = def.getValueInit("assemblyFormat"); + return isa(valueInit) || isa(valueInit); +} + +StringRef tblgen::Operator::getAssemblyFormat() const { + return TypeSwitch(def.getValueInit("assemblyFormat")) + .Case( + [&](auto *init) { return init->getValue(); }); +} + void tblgen::Operator::print(llvm::raw_ostream &os) const { os << "op '" << getOperationName() << "'\n"; for (Argument arg : arguments) { diff --git a/mlir/tools/mlir-tblgen/OpDocGen.cpp b/mlir/tools/mlir-tblgen/OpDocGen.cpp --- a/mlir/tools/mlir-tblgen/OpDocGen.cpp +++ b/mlir/tools/mlir-tblgen/OpDocGen.cpp @@ -78,82 +78,140 @@ } } -static void emitOpDocForDialect(const Dialect &dialect, - const std::vector &ops, - const std::vector &types, - raw_ostream &os) { - os << "# Dialect '" << dialect.getName() << "' definition\n\n"; - emitIfNotEmpty(dialect.getSummary(), os); - emitIfNotEmpty(dialect.getDescription(), os); - - // TODO(b/143543720) Generate TOC where extension is not supported. - os << "[TOC]\n\n"; +/// Emit the given named constraint. +template +static void emitNamedConstraint(const T &it, raw_ostream &os) { + if (!it.name.empty()) + os << "`" << it.name << "`"; + else + os << "«unnamed»"; + os << " | " << it.constraint.getDescription() << "\n"; +} - // TODO(antiagainst): Add link between use and def for types - if (!types.empty()) - os << "## Type definition\n\n"; - for (auto type : types) { - os << "### " << type.getDescription() << "\n"; - emitDescription(type.getTypeDescription(), os); - os << "\n"; - } +//===----------------------------------------------------------------------===// +// Operation Documentation +//===----------------------------------------------------------------------===// - if (!ops.empty()) - os << "## Operation definition\n\n"; - for (auto op : ops) { - os << "### " << op.getOperationName() << " (" << op.getQualCppClassName() - << ")"; - - // Emit summary & description of operator. - if (op.hasSummary()) - os << "\n" << op.getSummary() << "\n"; - os << "\n#### Description:\n\n"; - if (op.hasDescription()) - mlir::tblgen::emitDescription(op.getDescription(), os); - - // Emit operands & type of operand. All operands are numbered, some may be - // named too. - os << "\n#### Operands:\n\n"; - for (const auto &operand : op.getOperands()) { - os << "1. "; - if (!operand.name.empty()) - os << "`" << operand.name << "`: "; - else - os << "«unnamed»: "; - os << operand.constraint.getDescription() << "\n"; - } +/// Emit the assembly format of an operation. +static void emitAssemblyFormat(StringRef opName, StringRef format, + raw_ostream &os) { + os << "\nSyntax:\n\n```\noperation ::= `" << opName << "` "; + + // Print the assembly format aligned. + unsigned indent = strlen("operation ::= "); + std::pair split = format.split('\n'); + os << split.first.trim() << "\n"; + do { + split = split.second.split('\n'); + StringRef formatChunk = split.first.trim(); + if (!formatChunk.empty()) + os.indent(indent) << formatChunk << "\n"; + } while (!split.second.empty()); + os << "```\n\n"; +} - // Emit attributes. +static void emitOpDoc(Operator op, raw_ostream &os) { + os << llvm::formatv("### `{0}` ({1})\n", op.getOperationName(), + op.getQualCppClassName()); + + // Emit the summary, syntax, and description if present. + if (op.hasSummary()) + os << "\n" << op.getSummary() << "\n"; + if (op.hasAssemblyFormat()) + emitAssemblyFormat(op.getOperationName(), op.getAssemblyFormat().trim(), + os); + if (op.hasDescription()) + mlir::tblgen::emitDescription(op.getDescription(), os); + + // Emit attributes. + if (op.getNumAttributes() != 0) { // TODO: Attributes are only documented by TableGen name, with no further // info. This should be improved. os << "\n#### Attributes:\n\n"; - if (op.getNumAttributes() > 0) { - os << "| Attribute | MLIR Type | Description |\n" - << "| :-------: | :-------: | ----------- |\n"; - } - for (auto namedAttr : op.getAttributes()) { - os << "| `" << namedAttr.name << "` | `" - << namedAttr.attr.getStorageType() << "` | " - << namedAttr.attr.getDescription() << " attribute |\n"; + os << "| Attribute | MLIR Type | Description |\n" + << "| :-------: | :-------: | ----------- |\n"; + for (const auto &it : op.getAttributes()) { + StringRef storageType = it.attr.getStorageType(); + os << "`" << it.name << "` | " << storageType << " | " + << it.attr.getDescription() << "\n"; } + } - // Emit results. + // Emit each of the operands. + if (op.getNumOperands() != 0) { + os << "\n#### Operands:\n\n"; + os << "| Operand | Description |\n" + << "| :-----: | ----------- |\n"; + for (const auto &it : op.getOperands()) + emitNamedConstraint(it, os); + } + + // Emit results. + if (op.getNumResults() != 0) { os << "\n#### Results:\n\n"; - for (unsigned i = 0, e = op.getNumResults(); i < e; ++i) { - os << "1. "; - auto name = op.getResultName(i); - if (name.empty()) - os << "«unnamed»: "; - else - os << "`" << name << "`: "; - os << op.getResultTypeConstraint(i).getDescription() << "\n"; - } + os << "| Result | Description |\n" + << "| :----: | ----------- |\n"; + for (const auto &it : op.getResults()) + emitNamedConstraint(it, os); + } - os << "\n"; + // Emit successors. + if (op.getNumSuccessors() != 0) { + os << "\n#### Successors:\n\n"; + os << "| Successor | Description |\n" + << "| :-------: | ----------- |\n"; + for (const auto &it : op.getSuccessors()) + emitNamedConstraint(it, os); } + + os << "\n"; } static void emitOpDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { + auto opDefs = recordKeeper.getAllDerivedDefinitions("Op"); + + os << "\n"; + for (const llvm::Record *opDef : opDefs) + emitOpDoc(Operator(opDef), os); +} + +//===----------------------------------------------------------------------===// +// Type Documentation +//===----------------------------------------------------------------------===// + +static void emitTypeDoc(const Type &type, raw_ostream &os) { + os << "### " << type.getDescription() << "\n"; + emitDescription(type.getTypeDescription(), os); + os << "\n"; +} + +//===----------------------------------------------------------------------===// +// Dialect Documentation +//===----------------------------------------------------------------------===// + +static void emitDialectDoc(const Dialect &dialect, ArrayRef ops, + ArrayRef types, raw_ostream &os) { + os << "# '" << dialect.getName() << "' Dialect\n\n"; + emitIfNotEmpty(dialect.getSummary(), os); + emitIfNotEmpty(dialect.getDescription(), os); + + os << "[TOC]\n\n"; + + // TODO(antiagainst): Add link between use and def for types + if (!types.empty()) { + os << "## Type definition\n\n"; + for (const Type &type : types) + emitTypeDoc(type, os); + } + + if (!ops.empty()) { + os << "## Operation definition\n\n"; + for (const Operator &op : ops) + emitOpDoc(op, os); + } +} + +static void emitDialectDoc(const RecordKeeper &recordKeeper, raw_ostream &os) { const auto &opDefs = recordKeeper.getAllDerivedDefinitions("Op"); const auto &typeDefs = recordKeeper.getAllDerivedDefinitions("DialectType"); @@ -171,13 +229,24 @@ os << "\n"; for (auto dialectWithOps : dialectOps) - emitOpDocForDialect(dialectWithOps.first, dialectWithOps.second, - dialectTypes[dialectWithOps.first], os); + emitDialectDoc(dialectWithOps.first, dialectWithOps.second, + dialectTypes[dialectWithOps.first], os); } +//===----------------------------------------------------------------------===// +// Gen Registration +//===----------------------------------------------------------------------===// + +static mlir::GenRegistration + genOpRegister("gen-op-doc", "Generate dialect documentation", + [](const RecordKeeper &records, raw_ostream &os) { + emitOpDoc(records, os); + return false; + }); + static mlir::GenRegistration - genRegister("gen-op-doc", "Generate operation documentation", + genRegister("gen-dialect-doc", "Generate dialect documentation", [](const RecordKeeper &records, raw_ostream &os) { - emitOpDoc(records, os); + emitDialectDoc(records, os); return false; }); diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp --- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp +++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp @@ -1830,19 +1830,13 @@ // TODO(riverriddle) Operator doesn't expose all necessary functionality via // the const interface. Operator &op = const_cast(constOp); - - // Check if the operation specified the format field. - StringRef formatStr; - TypeSwitch(op.getDef().getValueInit("assemblyFormat")) - .Case( - [&](auto *init) { formatStr = init->getValue(); }); - if (formatStr.empty()) + if (!op.hasAssemblyFormat()) return; // Parse the format description. llvm::SourceMgr mgr; - mgr.AddNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(formatStr), - llvm::SMLoc()); + mgr.AddNewSourceBuffer( + llvm::MemoryBuffer::getMemBuffer(op.getAssemblyFormat()), llvm::SMLoc()); OperationFormat format(op); if (failed(FormatParser(mgr, format, op).parse())) { // Exit the process if format errors are treated as fatal.