blob: 317de8e8fac18f4f5f566880565da3b4c20dbe4b [file] [log] [blame]
// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Adapted from mlir-tblgen.cpp. Simply delegates through to MlirTblgenMain.
#include <optional>
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "mlir/TableGen/AttrOrTypeDef.h"
#include "mlir/TableGen/Dialect.h"
#include "mlir/TableGen/GenInfo.h"
#include "mlir/TableGen/Interfaces.h"
#include "mlir/TableGen/Operator.h"
#include "mlir/Tools/mlir-tblgen/MlirTblgenMain.h"
using namespace llvm;
using namespace mlir;
using namespace mlir::tblgen;
//===----------------------------------------------------------------------===//
// JSON Documentation Generation
//===----------------------------------------------------------------------===//
// Strips consistent leading whitespace from description text.
// Detects the pattern `= [{\n ...` and removes that leading whitespace
// from all lines. Preserves relative indentation for ASCII art, etc.
// Returns original text if parsing fails at any point.
static std::string cleanDescription(StringRef desc) {
if (desc.empty()) {
return "";
}
SmallVector<StringRef> lines;
desc.split(lines, '\n');
// Find minimum indentation (excluding empty lines).
// Use std::optional to avoid npos sentinel value bugs.
std::optional<size_t> minIndent;
for (StringRef line : lines) {
size_t firstNonWS = line.find_first_not_of(" \t");
if (firstNonWS != StringRef::npos) {
// Non-empty line found.
if (!minIndent || firstNonWS < *minIndent) {
minIndent = firstNonWS;
}
}
}
// If no non-empty lines or indentation is zero, return original.
if (!minIndent || *minIndent == 0) {
return desc.str();
}
// Strip the common indentation from all lines.
std::string result;
for (size_t i = 0; i < lines.size(); ++i) {
StringRef line = lines[i];
if (line.find_first_not_of(" \t") == StringRef::npos) {
// Empty/whitespace-only line - preserve it.
if (i > 0) {
result += '\n';
}
} else {
// Non-empty line - strip common indentation.
if (line.size() < *minIndent) {
// Line is shorter than expected indentation - bail cleanly.
// This shouldn't happen if logic is correct, but handle defensively.
return desc.str();
}
if (i > 0) {
result += '\n';
}
result += line.substr(*minIndent).str();
}
}
// Trim leading/trailing blank lines.
while (!result.empty() && result.front() == '\n') {
result.erase(0, 1);
}
while (!result.empty() && result.back() == '\n') {
result.pop_back();
}
return result;
}
// Cleans assembly format by replacing newlines with spaces.
// Assembly format strings in TableGen files may have newlines for readability,
// but they're ignored by the parser so we normalize to single line.
static std::string cleanAssemblyFormat(StringRef format) {
if (format.empty()) {
return "";
}
std::string result = format.str();
// Replace all newlines with spaces.
for (char &c : result) {
if (c == '\n') {
c = ' ';
}
}
// Collapse multiple spaces into single space.
size_t writePos = 0;
bool lastWasSpace = false;
for (size_t i = 0; i < result.size(); ++i) {
if (result[i] == ' ') {
if (!lastWasSpace) {
result[writePos++] = ' ';
lastWasSpace = true;
}
} else {
result[writePos++] = result[i];
lastWasSpace = false;
}
}
result.resize(writePos);
// Trim leading/trailing spaces.
while (!result.empty() && result.front() == ' ') {
result.erase(0, 1);
}
while (!result.empty() && result.back() == ' ') {
result.pop_back();
}
return result;
}
// Resolves relative path to absolute path using the defining record's location.
// This is best-effort resolution - returns original path if resolution fails.
// Paths in metadata are relative to the repository root (e.g.,
// "test/foo.mlir"), so we walk up from the .td file location to find the
// repository root. Note: Assumes IREE-style repo structure with "compiler/src/"
// directory.
static std::string resolveRelativePath(StringRef relativePath,
const Record *definingRecord) {
if (relativePath.empty()) {
return "";
}
// Get source location of the record (the .td file).
ArrayRef<SMLoc> locs = definingRecord->getLoc();
if (locs.empty() || !locs[0].isValid()) {
// No valid location - can't resolve, return original path.
return relativePath.str();
}
// Find buffer containing this location.
unsigned bufferID = llvm::SrcMgr.FindBufferContainingLoc(locs[0]);
if (bufferID == 0) {
// Buffer not found - return original path.
return relativePath.str();
}
// Get .td file path from buffer.
const MemoryBuffer *buffer = llvm::SrcMgr.getMemoryBuffer(bufferID);
if (!buffer) {
// Defensive: buffer should exist if bufferID != 0, but check anyway.
return relativePath.str();
}
StringRef tdFilePath = buffer->getBufferIdentifier();
// Find repository root by walking up from .td file directory.
// The repository root is assumed to be the directory containing
// "compiler/src/".
SmallString<256> currentDir(tdFilePath);
llvm::sys::path::remove_filename(
currentDir); // Start from .td file's directory.
SmallString<256> repoRoot;
bool foundRoot = false;
// Walk up the directory tree looking for "compiler/src/".
while (!currentDir.empty() && currentDir != "/") {
SmallString<256> compilerSrcPath(currentDir);
llvm::sys::path::append(compilerSrcPath, "compiler", "src");
// Check if this directory contains "compiler/src/".
bool isDir = false;
std::error_code ec = llvm::sys::fs::is_directory(compilerSrcPath, isDir);
if (!ec && isDir) {
// Found the repository root.
repoRoot = currentDir;
foundRoot = true;
break;
}
// If error checking directory, continue walking up (graceful degradation).
// Move up one directory.
llvm::sys::path::remove_filename(currentDir);
}
// Fallback: if root-finding failed, try extracting from .td file path.
if (!foundRoot) {
// Try to find "compiler/src/" in the .td file path itself.
StringRef tdFileStr(tdFilePath);
size_t compilerSrcPos = tdFileStr.find("compiler/src/");
if (compilerSrcPos != StringRef::npos) {
// Extract everything before "compiler/src/" as repo root.
repoRoot = tdFileStr.substr(0, compilerSrcPos);
foundRoot = true;
} else {
// Could not determine repository root - return original path unchanged.
// This is acceptable for non-IREE projects or different layouts.
return relativePath.str();
}
}
// Resolve relative path against repository root.
SmallString<256> absolutePath(repoRoot);
llvm::sys::path::append(absolutePath, relativePath);
// Normalize to remove . and ..
llvm::sys::path::remove_dots(absolutePath, /*remove_dot_dot=*/true);
return std::string(absolutePath.str());
}
// Emits source location information for a record.
// Gracefully degrades: if location can't be determined, emits nothing.
// This is acceptable as source location is supplementary information.
static void emitSourceLocation(const Record *def, const RecordKeeper &records,
json::OStream &J) {
(void)records; // Not needed - using global llvm::SrcMgr.
// Get the primary location (first element of location array).
ArrayRef<SMLoc> locs = def->getLoc();
if (locs.empty() || !locs[0].isValid()) {
// No location available - skip emitting sourceLocation attribute.
return;
}
SMLoc loc = locs[0];
// Find which buffer contains this location.
unsigned bufferID = llvm::SrcMgr.FindBufferContainingLoc(loc);
if (bufferID == 0) {
// Location not associated with a buffer - skip sourceLocation.
return;
}
// Get the file path from buffer.
const MemoryBuffer *buffer = llvm::SrcMgr.getMemoryBuffer(bufferID);
if (!buffer) {
// Defensive: buffer should exist if bufferID != 0, but check anyway.
return;
}
StringRef filename = buffer->getBufferIdentifier();
// Get line and column numbers.
std::pair<unsigned, unsigned> lineCol =
llvm::SrcMgr.getLineAndColumn(loc, bufferID);
// Emit as JSON.
J.attributeObject("sourceLocation", [&] {
J.attribute("file", filename);
J.attribute("line", lineCol.first);
J.attribute("column", lineCol.second);
});
}
// Emits parameters for a type or attribute.
static void emitParameters(ArrayRef<AttrOrTypeParameter> params,
json::OStream &J) {
if (params.empty()) {
return;
}
J.attributeArray("parameters", [&] {
for (const auto &param : params) {
J.object([&] {
if (!param.isAnonymous()) {
J.attribute("name", param.getName());
}
J.attribute("cppType", param.getCppType());
if (auto summary = param.getSummary()) {
J.attribute("summary", *summary);
}
if (param.isOptional()) {
J.attribute("isOptional", true);
if (auto defaultValue = param.getDefaultValue()) {
J.attribute("defaultValue", *defaultValue);
}
} else {
J.attribute("isOptional", false);
}
if (auto constraint = param.getConstraint()) {
J.attributeObject("constraint", [&] {
if (!constraint->getSummary().empty()) {
J.attribute("summary", constraint->getSummary());
}
if (!constraint->getDescription().empty()) {
J.attribute("description",
cleanDescription(constraint->getDescription()));
}
});
}
});
}
});
}
// Emits traits for a type or attribute.
static void emitTraits(ArrayRef<Trait> traits, json::OStream &J) {
if (traits.empty()) {
return;
}
J.attributeArray("traits", [&] {
for (const Trait &trait : traits) {
J.object([&] {
if (const auto *nt = dyn_cast<NativeTrait>(&trait)) {
J.attribute("kind", "NativeTrait");
J.attribute("trait", nt->getFullyQualifiedTraitName());
} else if (const auto *it = dyn_cast<InterfaceTrait>(&trait)) {
J.attribute("kind", "Interface");
J.attribute("trait", it->getFullyQualifiedTraitName());
} else {
J.attribute("kind", "Other");
}
});
}
});
}
// Emits builders for an operation.
static void emitBuilders(ArrayRef<Builder> builders, json::OStream &J) {
if (builders.empty()) {
return;
}
J.attributeArray("builders", [&] {
for (const Builder &builder : builders) {
J.object([&] {
// Emit parameters.
J.attributeArray("parameters", [&] {
for (const Builder::Parameter &param : builder.getParameters()) {
J.object([&] {
J.attribute("type", param.getCppType());
if (auto name = param.getName()) {
J.attribute("name", *name);
}
if (auto defaultValue = param.getDefaultValue()) {
J.attribute("defaultValue", *defaultValue);
}
});
}
});
// Check if deprecated.
if (auto deprecatedMsg = builder.getDeprecatedMessage()) {
J.attribute("deprecated", true);
J.attribute("deprecationMessage", *deprecatedMsg);
}
});
}
});
}
// Emits interface methods.
static void emitInterfaceMethods(ArrayRef<InterfaceMethod> methods,
json::OStream &J) {
if (methods.empty()) {
return;
}
J.attributeArray("methods", [&] {
for (const InterfaceMethod &method : methods) {
J.object([&] {
J.attribute("name", method.getName());
J.attribute("returnType", method.getReturnType());
J.attributeArray("arguments", [&] {
for (const auto &arg : method.getArguments()) {
J.object([&] {
J.attribute("type", arg.type);
J.attribute("name", arg.name);
});
}
});
J.attribute("isStatic", method.isStatic());
if (auto desc = method.getDescription()) {
J.attribute("description", cleanDescription(*desc));
}
// Check if method has a default implementation.
bool hasDefault = method.getDefaultImplementation().has_value();
J.attribute("hasDefaultImpl", hasDefault);
});
}
});
}
// Returns all operation definitions.
static std::vector<const Record *> getOpDefinitions(
const RecordKeeper &records) {
const Record *classDef = records.getClass("Op");
if (!classDef) {
return {};
}
std::vector<const Record *> defs;
for (const auto &def : records.getDefs()) {
if (def.second->isSubClassOf(classDef)) {
defs.push_back(def.second.get());
}
}
return defs;
}
// Returns all type definitions.
static std::vector<const Record *> getTypeDefinitions(
const RecordKeeper &records) {
const Record *classDef = records.getClass("TypeDef");
if (!classDef) {
return {};
}
std::vector<const Record *> defs;
for (const auto &def : records.getDefs()) {
if (def.second->isSubClassOf(classDef)) {
defs.push_back(def.second.get());
}
}
return defs;
}
// Returns all attribute definitions.
static std::vector<const Record *> getAttrDefinitions(
const RecordKeeper &records) {
const Record *classDef = records.getClass("AttrDef");
if (!classDef) {
return {};
}
std::vector<const Record *> defs;
for (const auto &def : records.getDefs()) {
if (def.second->isSubClassOf(classDef)) {
defs.push_back(def.second.get());
}
}
return defs;
}
// Returns all interface definitions (Op, Type, and Attr interfaces).
static std::vector<const Record *> getInterfaceDefinitions(
const RecordKeeper &records) {
std::vector<const Record *> defs;
// OpInterface, AttrInterface, TypeInterface.
for (const char *className :
{"OpInterface", "AttrInterface", "TypeInterface"}) {
const Record *classDef = records.getClass(className);
if (!classDef) {
continue;
}
for (const auto &def : records.getDefs()) {
if (def.second->isSubClassOf(classDef)) {
// Skip anonymous records generated by DeclareInterfaceMethods helpers.
// These are implementation details that duplicate named interfaces.
StringRef name = def.second->getName();
if (name.starts_with("anonymous_")) {
continue;
}
defs.push_back(def.second.get());
}
}
}
return defs;
}
// Returns all dialect definitions.
static std::vector<Dialect> getDialects(const RecordKeeper &records) {
auto dialectDefs = records.getAllDerivedDefinitions("Dialect");
std::vector<Dialect> dialects;
dialects.reserve(dialectDefs.size());
for (const Record *def : dialectDefs) {
dialects.emplace_back(def);
}
return dialects;
}
// Extracts dialect name from a fully qualified interface name.
// Tries multiple common namespace patterns:
// 1. ::mlir::iree_compiler::IREE::DialectName::ClassName (IREE pattern)
// 2. ::mlir::DialectName::ClassName (simple MLIR pattern)
// 3. Generic: second-to-last namespace component before ClassName
// Returns empty optional if extraction fails.
static std::optional<std::string> extractDialectFromNamespace(StringRef fqn) {
// Try IREE-specific pattern: ::mlir::iree_compiler::IREE::DialectName::
StringRef ireePrefix = "::mlir::iree_compiler::IREE::";
if (fqn.starts_with(ireePrefix)) {
StringRef afterPrefix = fqn.substr(ireePrefix.size());
size_t nextColon = afterPrefix.find("::");
if (nextColon != StringRef::npos) {
return afterPrefix.substr(0, nextColon).lower();
}
}
// Try simple MLIR pattern: ::mlir::DialectName::ClassName
StringRef mlirPrefix = "::mlir::";
if (fqn.starts_with(mlirPrefix)) {
StringRef afterMlir = fqn.substr(mlirPrefix.size());
size_t firstColon = afterMlir.find("::");
if (firstColon != StringRef::npos) {
StringRef candidate = afterMlir.substr(0, firstColon);
// Make sure there's another :: after this (i.e., ClassName exists).
StringRef afterCandidate = afterMlir.substr(firstColon + 2);
if (!afterCandidate.empty() &&
afterCandidate.find("::") == StringRef::npos) {
// Pattern matches: ::mlir::DialectName::ClassName
return candidate.lower();
}
}
}
// Generic fallback: extract second-to-last namespace component.
// Find last :: to get ClassName, then find previous :: for DialectName.
size_t lastColons = fqn.rfind("::");
if (lastColons != StringRef::npos && lastColons > 0) {
StringRef beforeLast = fqn.substr(0, lastColons);
size_t secondLastColons = beforeLast.rfind("::");
if (secondLastColons != StringRef::npos) {
StringRef dialectName = beforeLast.substr(secondLastColons + 2);
if (!dialectName.empty()) {
return dialectName.lower();
}
}
}
// Could not extract dialect name - return empty.
return std::nullopt;
}
// Emits structured constraint information for type unions.
// Extracts the allowedTypes list from AnyTypeOf/AllOfType constraints.
static void emitConstraintMetadata(const tblgen::Constraint &constraint,
json::OStream &J) {
const Record &def = constraint.getDef();
// Check if this is a union-type constraint (AnyTypeOf, AllOfType, etc.).
// These constraints have an "allowedTypes" field with the constituent types.
if (!def.getValue("allowedTypes")) {
return; // Not a union constraint, nothing to emit.
}
std::vector<const Record *> allowedTypes =
def.getValueAsListOfDefs("allowedTypes");
if (allowedTypes.empty()) {
return; // No types listed, skip emission.
}
// Emit structured constraint object.
J.attributeObject("constraint", [&] {
// Determine constraint kind from the class hierarchy.
std::string kind = "UnionType";
if (def.isSubClassOf("AnyTypeOf")) {
kind = "AnyTypeOf";
} else if (def.isSubClassOf("AllOfType")) {
kind = "AllOfType";
} else if (def.isSubClassOf("TypeConstraint")) {
kind = "TypeConstraint";
}
J.attribute("kind", kind);
// Emit the list of allowed type names.
J.attributeArray("allowedTypes", [&] {
for (const Record *typeRecord : allowedTypes) {
J.value(typeRecord->getName());
}
});
});
}
// Emits common metadata fields (used by types, attrs, interfaces).
static void emitCommonMetadata(const Record *metadata,
const Record *definingRecord, json::OStream &J,
const char *relatedListName) {
if (!metadata) {
return;
}
J.attributeObject("metadata", [&] {
if (StringRef category = metadata->getValueAsString("category");
!category.empty()) {
J.attribute("category", category);
}
auto relatedList = metadata->getValueAsListOfStrings(relatedListName);
if (!relatedList.empty()) {
J.attributeArray(relatedListName, [&] {
for (StringRef related : relatedList) {
J.value(related);
}
});
}
if (StringRef testFile = metadata->getValueAsString("testFile");
!testFile.empty()) {
std::string absolutePath = resolveRelativePath(testFile, definingRecord);
J.attribute("testFile", absolutePath);
}
auto exampleRefs = metadata->getValueAsListOfStrings("exampleRefs");
if (!exampleRefs.empty()) {
J.attributeArray("exampleRefs", [&] {
for (StringRef ref : exampleRefs) {
J.value(ref);
}
});
}
auto textualExamples = metadata->getValueAsListOfStrings("textualExamples");
if (!textualExamples.empty()) {
J.attributeArray("textualExamples", [&] {
for (StringRef example : textualExamples) {
J.value(example);
}
});
}
auto tags = metadata->getValueAsListOfStrings("tags");
if (!tags.empty()) {
J.attributeArray("tags", [&] {
for (StringRef tag : tags) {
J.value(tag);
}
});
}
});
}
// Emits a single operation as JSON.
static void emitOperatorJSON(const Operator &op, const RecordKeeper &records,
json::OStream &J) {
const Record &def = op.getDef();
J.object([&] {
J.attribute("name", op.getOperationName());
// Dialect reference.
J.attribute("dialect", op.getDialect().getName());
// C++ class names.
J.attribute("cppClassName", op.getCppClassName());
std::string fullyQualifiedName = op.getDialect().getCppNamespace().str() +
"::" + op.getCppClassName().str();
J.attribute("fullyQualifiedName", fullyQualifiedName);
// Source location.
emitSourceLocation(&def, records, J);
if (op.hasSummary()) {
J.attribute("summary", cleanDescription(op.getSummary()));
}
if (op.hasDescription()) {
J.attribute("description", cleanDescription(op.getDescription()));
}
if (op.hasAssemblyFormat()) {
J.attribute("assemblyFormat",
cleanAssemblyFormat(op.getAssemblyFormat()));
}
// Custom metadata extraction (Util_OpDocMetadata).
if (const Record *metadata = def.getValueAsOptionalDef("docMetadata")) {
J.attributeObject("metadata", [&] {
// Extract category from OpDocGroup if present.
if (const Record *docGroup =
metadata->getValueAsOptionalDef("docGroup")) {
if (StringRef summary = docGroup->getValueAsString("summary");
!summary.empty()) {
J.attribute("category", summary);
}
}
auto relatedOps = metadata->getValueAsListOfStrings("relatedOps");
if (!relatedOps.empty()) {
J.attributeArray("relatedOps", [&] {
for (StringRef relOp : relatedOps) {
J.value(relOp);
}
});
}
if (StringRef testFile = metadata->getValueAsString("testFile");
!testFile.empty()) {
std::string absolutePath = resolveRelativePath(testFile, &def);
J.attribute("testFile", absolutePath);
}
auto exampleRefs = metadata->getValueAsListOfStrings("exampleRefs");
if (!exampleRefs.empty()) {
J.attributeArray("exampleRefs", [&] {
for (StringRef exampleRef : exampleRefs) {
J.value(exampleRef);
}
});
}
auto textualExamples =
metadata->getValueAsListOfStrings("textualExamples");
if (!textualExamples.empty()) {
J.attributeArray("textualExamples", [&] {
for (StringRef example : textualExamples) {
J.value(example);
}
});
}
auto tags = metadata->getValueAsListOfStrings("tags");
if (!tags.empty()) {
J.attributeArray("tags", [&] {
for (StringRef tag : tags) {
J.value(tag);
}
});
}
});
}
// Operands.
J.attributeArray("operands", [&] {
for (const auto &operand : op.getOperands()) {
J.object([&] {
J.attribute("name", operand.name);
// Basic type information.
if (!operand.constraint.getSummary().empty()) {
J.attribute("summary", operand.constraint.getSummary());
}
if (!operand.constraint.getDescription().empty()) {
J.attribute("description",
cleanDescription(operand.constraint.getDescription()));
}
// Type constraint details.
// Skip anonymous TableGen-generated type constraint names.
if (StringRef defName = operand.constraint.getDefName();
!defName.empty() && !defName.starts_with("anonymous_")) {
J.attribute("defName", defName);
}
// C++ type information.
if (StringRef cppType = operand.constraint.getCppType();
!cppType.empty()) {
J.attribute("cppType", cppType);
}
// Emit structured constraint metadata if this is a union type.
emitConstraintMetadata(operand.constraint, J);
// Variadic/optional flags.
J.attributeObject("properties", [&] {
J.attribute("isOptional", operand.isOptional());
J.attribute("isVariadic", operand.isVariadic());
J.attribute("isVariadicOfVariadic", operand.isVariadicOfVariadic());
});
});
}
});
// Results.
J.attributeArray("results", [&] {
for (const auto &result : op.getResults()) {
J.object([&] {
J.attribute("name", result.name);
// Basic type information.
if (!result.constraint.getSummary().empty()) {
J.attribute("summary", result.constraint.getSummary());
}
if (!result.constraint.getDescription().empty()) {
J.attribute("description",
cleanDescription(result.constraint.getDescription()));
}
// Type constraint details.
// Skip anonymous TableGen-generated type constraint names.
if (StringRef defName = result.constraint.getDefName();
!defName.empty() && !defName.starts_with("anonymous_")) {
J.attribute("defName", defName);
}
// C++ type information.
if (StringRef cppType = result.constraint.getCppType();
!cppType.empty()) {
J.attribute("cppType", cppType);
}
// Emit structured constraint metadata if this is a union type.
emitConstraintMetadata(result.constraint, J);
// Variadic/optional flags.
J.attributeObject("properties", [&] {
J.attribute("isOptional", result.isOptional());
J.attribute("isVariadic", result.isVariadic());
J.attribute("isVariadicOfVariadic", result.isVariadicOfVariadic());
});
});
}
});
// Traits.
J.attributeArray("traits", [&] {
for (const Trait &trait : op.getTraits()) {
if (const auto *nt = dyn_cast<NativeTrait>(&trait)) {
J.value(nt->getFullyQualifiedTraitName());
} else if (const auto *it = dyn_cast<InterfaceTrait>(&trait)) {
J.value(it->getFullyQualifiedTraitName());
}
}
});
// Builders.
emitBuilders(op.getBuilders(), J);
});
}
// Emits a single type as JSON.
static void emitTypeJSON(const Record &def, const RecordKeeper &records,
json::OStream &J) {
// Wrap in TypeDef to access TableGen APIs.
TypeDef type(&def);
J.object([&] {
J.attribute("name", def.getName());
// Dialect reference.
Dialect dialect = type.getDialect();
J.attribute("dialect", dialect.getName());
// C++ class names.
J.attribute("cppClassName", type.getCppClassName());
std::string fullyQualifiedName =
(dialect.getCppNamespace() + "::" + type.getCppClassName()).str();
J.attribute("fullyQualifiedName", fullyQualifiedName);
// Source location.
emitSourceLocation(&def, records, J);
// Mnemonic and assembly format.
if (def.getValue("mnemonic")) {
if (StringRef mnemonic = def.getValueAsString("mnemonic");
!mnemonic.empty()) {
J.attribute("mnemonic", mnemonic);
}
}
if (auto assemblyFormat = type.getAssemblyFormat()) {
J.attribute("assemblyFormat", cleanAssemblyFormat(*assemblyFormat));
}
// Documentation.
if (def.getValue("summary")) {
if (StringRef summary = def.getValueAsString("summary");
!summary.empty()) {
J.attribute("summary", cleanDescription(summary));
}
}
if (def.getValue("description")) {
if (StringRef desc = def.getValueAsString("description"); !desc.empty()) {
J.attribute("description", cleanDescription(desc));
}
}
// Parameters.
emitParameters(type.getParameters(), J);
// Traits.
emitTraits(type.getTraits(), J);
// Emit custom metadata (only if the field exists).
if (def.getValue("docMetadata")) {
if (const Record *metadata = def.getValueAsOptionalDef("docMetadata")) {
emitCommonMetadata(metadata, &def, J, "relatedTypes");
}
}
});
}
// Emits a single attribute as JSON.
static void emitAttrJSON(const Record &def, const RecordKeeper &records,
json::OStream &J) {
// Wrap in AttrDef to access TableGen APIs.
AttrDef attr(&def);
J.object([&] {
J.attribute("name", def.getName());
// Dialect reference.
Dialect dialect = attr.getDialect();
J.attribute("dialect", dialect.getName());
// C++ class names.
J.attribute("cppClassName", attr.getCppClassName());
std::string fullyQualifiedName =
(dialect.getCppNamespace() + "::" + attr.getCppClassName()).str();
J.attribute("fullyQualifiedName", fullyQualifiedName);
// Source location.
emitSourceLocation(&def, records, J);
// Mnemonic and assembly format.
if (def.getValue("mnemonic")) {
if (StringRef mnemonic = def.getValueAsString("mnemonic");
!mnemonic.empty()) {
J.attribute("mnemonic", mnemonic);
}
}
if (auto assemblyFormat = attr.getAssemblyFormat()) {
J.attribute("assemblyFormat", cleanAssemblyFormat(*assemblyFormat));
}
// Documentation.
if (def.getValue("summary")) {
if (StringRef summary = def.getValueAsString("summary");
!summary.empty()) {
J.attribute("summary", cleanDescription(summary));
}
}
if (def.getValue("description")) {
if (StringRef desc = def.getValueAsString("description"); !desc.empty()) {
J.attribute("description", cleanDescription(desc));
}
}
// Parameters.
emitParameters(attr.getParameters(), J);
// Traits.
emitTraits(attr.getTraits(), J);
// Emit custom metadata (only if the field exists).
if (def.getValue("docMetadata")) {
if (const Record *metadata = def.getValueAsOptionalDef("docMetadata")) {
emitCommonMetadata(metadata, &def, J, "relatedAttrs");
}
}
});
}
// Emits a single interface as JSON.
static void emitInterfaceJSON(const Record &def, const RecordKeeper &records,
json::OStream &J) {
// Wrap in Interface to access TableGen APIs.
Interface interface(&def);
J.object([&] {
J.attribute("name", def.getName());
// C++ class names.
std::string fullyQualifiedName = interface.getFullyQualifiedName();
StringRef fqnRef(fullyQualifiedName);
// Try to extract dialect name from namespace using generic patterns.
if (auto dialectName = extractDialectFromNamespace(fqnRef)) {
J.attribute("dialect", *dialectName);
}
// Extract class name from fully qualified name (part after last "::").
size_t lastColons = fqnRef.rfind("::");
if (lastColons != StringRef::npos) {
J.attribute("cppClassName", fqnRef.substr(lastColons + 2));
} else {
J.attribute("cppClassName", fqnRef);
}
J.attribute("fullyQualifiedName", fullyQualifiedName);
// Source location.
emitSourceLocation(&def, records, J);
// Determine interface type.
std::string interfaceType = "unknown";
if (def.isSubClassOf("OpInterface")) {
interfaceType = "op";
} else if (def.isSubClassOf("TypeInterface")) {
interfaceType = "type";
} else if (def.isSubClassOf("AttrInterface")) {
interfaceType = "attr";
}
J.attribute("interfaceType", interfaceType);
// Documentation.
if (def.getValue("summary")) {
if (StringRef summary = def.getValueAsString("summary");
!summary.empty()) {
J.attribute("summary", cleanDescription(summary));
}
}
if (auto desc = interface.getDescription()) {
J.attribute("description", cleanDescription(*desc));
}
// Base interfaces.
auto baseInterfaces = interface.getBaseInterfaces();
if (!baseInterfaces.empty()) {
J.attributeArray("baseInterfaces", [&] {
for (const Interface &base : baseInterfaces) {
J.value(base.getFullyQualifiedName());
}
});
}
// Methods.
emitInterfaceMethods(interface.getMethods(), J);
// Emit custom metadata (only if the field exists).
if (def.getValue("docMetadata")) {
if (const Record *metadata = def.getValueAsOptionalDef("docMetadata")) {
emitCommonMetadata(metadata, &def, J, "relatedInterfaces");
}
}
});
}
// Main JSON generation function.
static bool emitDialectJSON(const RecordKeeper &records, raw_ostream &os) {
// Get all definitions.
auto dialects = getDialects(records);
auto opDefs = getOpDefinitions(records);
auto typeDefs = getTypeDefinitions(records);
auto attrDefs = getAttrDefinitions(records);
auto interfaceDefs = getInterfaceDefinitions(records);
// Collect categories from operations.
std::map<std::string, std::string> categories;
for (const Record *def : opDefs) {
if (const Record *metadata = def->getValueAsOptionalDef("docMetadata")) {
if (const Record *docGroup =
metadata->getValueAsOptionalDef("docGroup")) {
StringRef categoryName = docGroup->getValueAsString("summary");
StringRef categoryDesc = docGroup->getValueAsString("description");
if (!categoryName.empty()) {
// Store category if not already present.
categories.try_emplace(categoryName.str(), categoryDesc.str());
}
}
}
}
json::OStream J(os, 2); // Indent with 2 spaces.
J.object([&] {
// Dialects.
if (!dialects.empty()) {
J.attributeObject("dialects", [&] {
for (const Dialect &dialect : dialects) {
J.attributeObject(dialect.getName(), [&] {
// C++ class names.
J.attribute("cppClassName", dialect.getCppClassName());
std::string fullyQualifiedName =
(dialect.getCppNamespace() + "::" + dialect.getCppClassName())
.str();
J.attribute("fullyQualifiedName", fullyQualifiedName);
// Source location.
emitSourceLocation(dialect.getDef(), records, J);
if (!dialect.getSummary().empty()) {
J.attribute("summary", cleanDescription(dialect.getSummary()));
}
if (!dialect.getDescription().empty()) {
J.attribute("description",
cleanDescription(dialect.getDescription()));
}
// Dependent dialects.
ArrayRef<StringRef> deps = dialect.getDependentDialects();
if (!deps.empty()) {
J.attributeArray("dependentDialects", [&] {
for (StringRef dep : deps) {
J.value(dep);
}
});
}
// Feature flags (only emit if any are non-default).
if (dialect.hasCanonicalizer() ||
dialect.hasConstantMaterializer() ||
!dialect.useDefaultAttributePrinterParser() ||
!dialect.useDefaultTypePrinterParser() ||
dialect.isExtensible()) {
J.attributeObject("features", [&] {
J.attribute("hasCanonicalizer", dialect.hasCanonicalizer());
J.attribute("hasConstantMaterializer",
dialect.hasConstantMaterializer());
J.attribute("useDefaultAttributePrinterParser",
dialect.useDefaultAttributePrinterParser());
J.attribute("useDefaultTypePrinterParser",
dialect.useDefaultTypePrinterParser());
J.attribute("isExtensible", dialect.isExtensible());
});
}
});
}
});
}
// Categories.
if (!categories.empty()) {
J.attributeObject("categories", [&] {
for (const auto &category : categories) {
J.attributeObject(category.first, [&] {
if (!category.second.empty()) {
J.attribute("description", category.second);
}
});
}
});
}
// Operations.
if (!opDefs.empty()) {
J.attributeArray("operations", [&] {
for (const Record *def : opDefs) {
emitOperatorJSON(Operator(def), records, J);
}
});
}
// Types.
if (!typeDefs.empty()) {
J.attributeArray("types", [&] {
for (const Record *def : typeDefs) {
emitTypeJSON(*def, records, J);
}
});
}
// Attributes.
if (!attrDefs.empty()) {
J.attributeArray("attributes", [&] {
for (const Record *def : attrDefs) {
emitAttrJSON(*def, records, J);
}
});
}
// Interfaces.
if (!interfaceDefs.empty()) {
J.attributeArray("interfaces", [&] {
for (const Record *def : interfaceDefs) {
emitInterfaceJSON(*def, records, J);
}
});
}
});
return false;
}
// Register the JSON dialect doc generation backend.
GenRegistration genDialectJSON("gen-dialect-json",
"Generate JSON documentation for a dialect",
emitDialectJSON);
//===----------------------------------------------------------------------===//
// Other Generators
//===----------------------------------------------------------------------===//
// Generator that prints records.
GenRegistration genPrintRecords("print-records", "Print all records to stdout",
[](const RecordKeeper &records,
raw_ostream &os) {
os << records;
return false;
});
int main(int argc, char **argv) { return MlirTblgenMain(argc, argv); }